2

I love playing with enums.

Basicly I need a built-in function that returns me all enum flags that are set in an Integer.

Basicly when I have 110 as Tnteger value (6) for an enum with values a = 1, b = 2 and c = 4 I want to get b and c as return value.

I made this pretty piece of code but I feel like there must be something built-in in 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 is defined as Enum with this body:

   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;
    }

Maybe a keyword what I have to look for?

Patrick
  • 29
  • 1
  • 6
  • How is `GoalType` defined? – Klitos Kyriacou Jun 15 '17 at 14:38
  • Are you trying to emulate the `[Flags]` attribute of C# enums in Java? – Klitos Kyriacou Jun 15 '17 at 14:40
  • @KlitosKyriacou updated text! – Patrick Jun 15 '17 at 14:43
  • @KlitosKyriacou Yes I am ._. – Patrick Jun 15 '17 at 14:43
  • 5
    Your code exactly duplicates the functionality and implementation of [EnumSet](http://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html). – VGR Jun 15 '17 at 14:48
  • Wether I return an array of enum flags or an EnumSet. I still cannot convert an Integer into these flags. – Patrick Jun 15 '17 at 14:59
  • 1
    For the standard Java support for enums, see the Java tutorial: [Enum types](https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html). – Andy Thomas Jun 15 '17 at 15:00
  • See https://stackoverflow.com/questions/1499833/convert-a-two-byte-bit-mask-into-a-enumset – Klitos Kyriacou Jun 15 '17 at 15:01
  • Note that `lstBits.get(0)` is the _most significant_ set bit of `nval` (which is always 1). On the other hand, `arGoals[0]` is the _lowest_-valued enum. So the two don't correspond. Perhaps make `intToBinary` return a little-endian list, instead of a big-endian one? (Though in real-world code, noone would create a list of boolean objects from an int; they would just look at the bits of the int directly - it's just as easy as looking at boolean list elements.) – Klitos Kyriacou Jun 15 '17 at 15:17
  • This implementation actually duplicates `EnumSet`'s one, but there doesn't seem to be a built-in method that receives a int representing flags and returns the corresponding subset. – fps Jun 15 '17 at 15:53

3 Answers3

2

As said in the comments of the question, EnumSet (and EnumMap) are the way to go if you want to store enums or use them as keys, etc. However, neither of them offers the operation you want to implement. So for this specific task, you're on your own, though this doesn't mean you can't use a bunch of very useful classes from the JDK that fit this task like a glove...

First of all, you don't need to make each element of your enum encapsulate an int value. So let's redefine your enum to be just an enum:

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

Then, let's create a method that takes the flags as input and returns an array of enum values:

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);
}

This creates a BitSet from the flags argument and then calls the BitSet.stream method, which returns an IntStream of indices for which the bitset contains a bit on, e.g. if flags is 1000 1010, the stream will be 1, 3, 7 (this is, from lowest to highest bit).

Then we filter these indices to keep only the ones that are within the enum's valid ordinal values. (This is completely optional, but very healthy; otherwise you might get an ArrayIndexOutOfBoundsException if the flags contain an index greater than the enum's maximum ordinal value).

After this check, we map the index to the actual enum value, by getting it from an array that contains all the enum values and finally we create and return an array with the desired enum values.

Sample usage:

byte[] flags = new byte[]{6};

GoalType[] goalTypes = this.matchingFlags(flags);

System.out.println(Arrays.toString(goalTypes)); // [BREAK, PLACE]

If you want, you can collect to an EnumSet instead of to an array. Here's a generic version that works for every enum:

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)));
}

Usage:

byte[] flags = new byte[]{6};

EnumSet<GoalType> goalTypes = getEnumValuesForFlags(GoalType.class, flags);

System.out.println(goalTypes); // [BREAK, PLACE]
fps
  • 33,623
  • 8
  • 55
  • 110
2

@Federico's answer is good, but sometimes you just cannot rely on Java's integers backing the enums. Sometimes you need to guarantee a specific mapping. For these times, there are two reasonable approaches, and which you use depends on the situation.

First Method

The preferred way, assuming you need to guarantee the mapping:

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;
    }
}

This allows you to guarantee the specific mappings that you end up with, which in my case has been necessary a lot in recent work.

Sometimes the specific mapping is not an integral (no pun) part of the enumeration. Sometimes some other object needs to view the enumeration as if it had a specific mapping. In those cases, it is generally preferred to not put the mapping in the enumeration itself, as was done above, since the enumeration should not know about the mapping in this case.

Second Method

In this case, you basically just create HashMaps to do the lookup for you. It can be annoying, especially if you have a lot of values, but it can be worth it.

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); }
}

In fact, this allows you to map any object to any other object, not just enum-to-int/int-to-enum, but it does handle the enum-int-enum situation well.

Now when you want to convert, you just MyEnumerationConversion.toEnum(2); or MyEnumerationConversion.toInt(MyEnumeration.EnumB);

Note: Although the generic parameter and the function arguments are Integer, you can supply an int to them, as Java will convert between int and Integer for you in a conversion called "boxing" (int->Integer) or "unboxing" (Integer->int). Using Integer was necessary here since Java does not support primitive types as generic parameters.

In code I have done recently, I have used both of the above two versions - the value as constructor argument, and the Map version. The Map (second) version, I used for exactly the situation you are encountering, mapping bit flags in integers to a set of enumeration values, and the other way around. And I used EnumSet as the return value for the int-to-enum conversion and I used EnumSet also as the parameter to the function that did the enum-to-int conversion.

Bit Maps

Below two code snippets assume First Method. If using Second Method, then replace MyEnumeration.toInt/MyEnumeration.fromInt with the appropriate alternative methods.

So now you need a bit map from your 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;
}

Or an EnumSet from your bit map?

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;
}

Optimization note concerning First Method

Above I dropped the simple comment in a code example // for improved performance, replace this lookup with a static HashMap. When I wrote that, I was completely not thinking at the moment about the static nature of an Enum.

Unfortunately, you cannot (at time of this writing) do something like this:

// 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); }
}

That will result in a compile error about using the static field in the initializer.

Instead, you will need to perform a delayed-initialization on the map. You should be able to do that by initializing map to null, checking if(map != null) in fromInt, and returning the map.get result, otherwise (if map is null, which it will be the first time fromInt is called), create the map then return the get result.

// 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);
    }
}
Loduwijk
  • 1,950
  • 1
  • 16
  • 28
  • Yes, you are right. Sometimes you need to have an explicit mapping from int (or any other object) to enums and viceversa. My answer heavily relies on the declaration order of the enum values (the ordinal), however I wanted to show the usage of bitset and intstream. Your answer deserves a +1 as well :) – fps Jun 15 '17 at 19:52
  • 1
    @FedericoPeraltaSchaffner And your answer is also better when a guaranteed mapping is not necessary. No need to waste time if `values()`/`ordinal()` are sufficient for the task. My answer is the jackhammer; if all you need to do is break an ice-cube the jackhammer leaves a mess that is not worth it. And breaking ice-cubes is much more common than breaking concrete, which is why I do not own a jackhammer. Unfortunately, I've been coming up against a lot of "concrete" in my development at work recently. And thank you for reminding me to +1; I forgot to hit that on yours - now it's done. – Loduwijk Jun 15 '17 at 20:26
1

The good old C way - define bit masks for these enums, for example:

public static final int MASK_B = 0x0002;

Then later the bit flag can be easily calculated by:

int bitFlag = nval & MASK_B

[Edit] Yeah, just use EnumSet as @VGR suggested.

jingx
  • 3,698
  • 3
  • 24
  • 40
  • This answer still deserves a +1, despite requiring more maintenance. Sometimes you just need to handle everything yourself. I have been running into that recently, as I have been needing to map enums to integers as well, and I need to guarantee a specific mapping, that is A MUST = 1, B MUST = 2, etc.. The integers backing enums by default in Java do not allow me to make such a guarantee. – Loduwijk Jun 15 '17 at 18:58
  • However, it might be easier to give your enum an integer value that you supply to its constructor. `enum MyEnum { A(1), B(2), etc.; private int value; MyEnum(int n) { value = n; } public int getValue() { return value; } }` Then you can get A's integer value by doing `A.getValue();` – Loduwijk Jun 15 '17 at 19:00
  • And I decided to turn my comment into an answer to expand on it. See my answer for more details. – Loduwijk Jun 15 '17 at 19:19