从整数/字节[]获取枚举标志

时间:2017-06-15 14:35:03

标签: java enums binary

我喜欢玩enums。

基本上我需要一个内置函数,它返回我在整数中设置的所有枚举标志。

基本上,当我得到110为Tnteger值(6)的枚举值为a = 1,b = 2且c = 4时我想得到b和c作为返回值。

我制作了这段漂亮的代码,但我觉得Java 8必须内置一些东西。

   public static GoalType[] flags(int nval)
    {
        ArrayList<Boolean> lstBits = new ArrayList<>();

        intToBinary(nval, lstBits);

        ArrayList<GoalType> goals = new ArrayList<>();

        GoalType[] arGoals = GoalType.values();

        for (int i = 0; i < lstBits.size(); i++)
        {
            if (lstBits.get(i))
                goals.add(arGoals[i]);
        }

        return goals.toArray(new GoalType[0]);
    }

    private static void intToBinary(int i, List<Boolean> lst)
    {
        int remainder = 0;

        if (i <= 1)
        {
            lst.add(i == 0 ? false : true);
        }
        else
        {

            remainder = i % 2;

            intToBinary(i >> 1, lst);
            lst.add(remainder == 0 ? false : true);
        }
    }

将GoalType定义为具有此正文的枚举:

   Undefined(1 << 0), Break(1 << 1), Place(1 << 2), KillMob(1 << 3), KillLPlayer(1 << 4), Deliver(1 << 5), Vote(1 << 6), Search(1 << 7);

    public final int value;

    GoalType(int nGoal)
    {
        value = nGoal;
    }

也许我需要找一个关键字?

3 个答案:

答案 0 :(得分:2)

如问题评论中所述,undefined(和EnumSet)是您想要存储枚举或将其用作键等的方法。但是,它们都没有提供您要实施的操作。因此,对于这项具体任务,您可以自己动手,但这并不意味着您无法使用JDK中的一组非常有用的类来完成这项任务,就像手套一样...... / p>

首先,您不需要使枚举的每个元素都封装EnumMap值。所以,让我们重新定义你的枚举只是一个枚举:

int

然后,让我们创建一个方法,将标志作为输入并返回一个枚举值数组:

enum GoalType {
    UNDEFINED, BREAK, PLACE, KILL_MOB, KILL_PLAYER, DELIVER, VOTE, SEARCH
}

这将从public GoalType[] matchingFlags(byte[] flags) { GoalType[] values = GoalType.values(); return BitSet.valueOf(flags).stream() .filter(n -> n < values.length) // bounds check .mapToObj(n -> values[n]) .toArray(GoalType[]::new); } 参数创建BitSet,然后调用BitSet.stream方法,该方法返回IntStream个索引,其中bitset包含一个位,例如如果flags为flags,则流将为1000 1010(这是从最低位到最高位)。

然后我们过滤这些索引以仅保留enum的有效序数值内的索引。 (这是完全可选的,但非常健康;否则如果标志包含的索引大于枚举的最大序数值,则可能会得到1, 3, 7

在检查之后,我们将索引映射到实际的枚举值,方法是从包含所有枚举值的数组中获取它,最后我们创建并返回一个包含所需枚举值的数组。

样本用法:

ArrayIndexOutOfBoundsException

如果需要,您可以收集到byte[] flags = new byte[]{6}; GoalType[] goalTypes = this.matchingFlags(flags); System.out.println(Arrays.toString(goalTypes)); // [BREAK, PLACE] 而不是数组。这是一个适用于每个枚举的通用版本:

EnumSet

用法:

public static <T extends Enum<T>> EnumSet<T> getEnumValuesForFlags(
        Class<T> enumType,
        byte[] flags) {

    T[] values = enumType.getEnumConstants();

    return BitSet.valueOf(flags).stream()
        .filter(n -> n < values.length) // bounds check
        .mapToObj(n -> values[n])
        .collect(Collectors.toCollection(() -> EnumSet.noneOf(enumType)));
}

答案 1 :(得分:1)

好的旧C方式 - 为这些枚举定义位掩码,例如:

public static final int MASK_B = 0x0002;

之后,可以通过以下方式轻松计算位标志:

int bitFlag = nval & MASK_B

[编辑]是的,只需使用EnumSet作为@VGR建议。

答案 2 :(得分:1)

@ Federico的答案很好,但有时你不能依赖Java支持枚举的整数。有时您需要保证特定的映射。在这些时候,有两种合理的方法,你使用它们取决于具体情况。

第一种方法

首选方式,假设您需要保证映射:

enum MyEnumeration
{
    // Normally you may have just done as in the following commented line:
//  EnumA, EnumB, EnumC;
    // Instead you should...

    // Give the enum a constructor in which you supply the mapping
    EnumA (1),
    EnumB (2),
    EnumC (3);

    private int value;

    MyEnumeration(int n)
    {
        value = n;
    }

    // Now you can request the mapping directly from any enum value too
    public int getValue()
    {
        return value;
    }

    // You can also request the enum value by providing the integer
    public static MyEnumeration fromValue(int value)
    {
        // for improved performance (constant time), replace this lookup with a static HashMap
        for(MyEnumeration e : values())
            if(e.value == value)
                return e;
        return null;
    }
}

这使您可以保证最终的特定映射,在我的案例中,这在最近的工作中是必不可少的。

有时,特定的映射不是枚举的一个整体(无双关语)部分。有时,某些其他对象需要查看枚举,就好像它具有特定的映射一样。在这些情况下,通常首选将映射放在枚举本身中,如上所述,因为在这种情况下枚举不应该知道映射。

第二种方法

在这种情况下,您基本上只需创建HashMap来为您执行查找。它可能很烦人,特别是如果你有很多价值观,但它是值得的。

MyEnumerationConversion
{
    private static Map<MyEnumeration, Integer> toInt = new HashMap<MyEnumeration, Integer>();
    private static Map<Integer, MyEnumeration> toEnum = new HashMap<Integer, MyEnumeration>();

    public static void initialize()
    {
        // populate the maps here however you want
    }

    public static int toInt(MyEnumeration e) { return toInt.get(e); }
    public static MyEnumeration toEnum(Integer i) { return toEnum.get(i); }
}

实际上,这允许您将任何对象映射到任何其他对象,而不仅仅是enum-to-int / int-to-enum,但它确实很好地处理了enum-int-enum情况。

现在,当您想转换时,只需MyEnumerationConversion.toEnum(2);MyEnumerationConversion.toInt(MyEnumeration.EnumB);

注意:虽然泛型参数和函数参数是Integer,但您可以为它们提供int,因为Java将为您在intInteger之间进行转换在称为“boxing”(int-&gt; Integer)或“unboxing”(Integer-&gt; int)的转换中。因为Java不支持基本类型作为通用参数,所以在这里使用Integer是必要的。

在我最近完成的代码中,我使用了上述两个版本 - 作为构造函数参数的值和Map版本。 Map(第二个)版本,我用于您遇到的情况,将整数中的位标记映射到一组枚举值,反之亦然。我使用EnumSet作为int-to-enum转换的返回值,我还使用EnumSet作为执行enum-to-int转换的函数的参数。

位图

以下两个代码段假设为First Method。如果使用Second Method,请使用适当的替代方法替换MyEnumeration.toInt / MyEnumeration.fromInt

现在您需要来自EnumSet的位图?

// You could use a stream instead. I just happen to think they are less readable
public int toBitMap(EnumSet set)
{
    int map = 0;
    for(MyEnumeration e : set)
        map |= e.getValue();
    return map;
}

或来自位图的EnumSet

public EnumSet<MyEnumeration> fromBitMap(int map)
{
    int highestOneBit = Integer.highestOneBit(map);
    EnumSet<MyEnumeration> enumSet = EnumSet.noneOf(MyEnumeration.class);

    // this loop will iterate over powers of two up to map's highest set bit
    // that is: 1, 2, 4, 8, etc., but will not iterate farther than needed
    for(int i = 1; i <= highestOneBit; i <<= 1)
        if( (map&i) != 0 )
            enumSet.add( MyEnumeration.fromValue(i) );

    return enumSet;
}

关于第一种方法的优化说明

上面我在代码示例// for improved performance, replace this lookup with a static HashMap中删除了简单注释。当我写这篇文章时,我完全没有想到Enum的静态性质。

不幸的是,你不能(在写这篇文章的时候)做这样的事情:

// BAD code
enum MyEnum
{
    A (5), B (10);

    int v;
    static Map<Integer, MyEnum> map = new HashMap<Integer, MyEnum>();

    MyEnum(int _v) { v = _v; map.put(v, this); }

    MyEnum fromInt(int v) { return map.get(v); }
}

这将导致在初始化程序中使用静态字段时出现编译错误。

相反,您需要在地图上执行延迟初始化。您应该可以通过将map初始化为null,检查if(map != null)中的fromInt并返回map.get结果来执行此操作,否则(如果map为null,它将会在第一次调用fromInt时,创建地图然后返回get结果。

// Better version
enum MyEnum
{
    A (5), B (10);

    private int value;
    private static Map<Integer, MyEnum> map = null;

    MyEnum(int v)
    {
        value = v;
    }

    public int getInt()
    {
        return value;
    }

    public static MyEnum fromInt(int v)
    {
        if(map != null)
        {
            return map.get(v);
        }
        else
        {
            initializeMap();
            return map.get(v);
        }
    }

    private void initializeMap()
    {
        map = new HashMap<Integer, MyEnum>();
        for(MyEnum e : values())
            map.put(e.getInt(), e);
    }
}