3

Lets say I have the following java enum

public enum Color {
  RED(10),
  GREEN(22),
  BLUE(33);

  private int value;

  Color(int value) {
    this.value = value;
  }

  public int intValue() {
    return value;
  }
}

In order to be able to obtain a color instance by a given integer value I need to add method like this:

public static Color getInstance(int value) {
  switch (value) {
    case 10: return RED;
    case 22: return GREEN;
    case 33: return BLUE;
    default: throw new IllegalArgumentException("Invalid color value");
  }
}

Is it possible this method to be autogenerated from the IDE? (Preferably IntelliJ)?

George
  • 7,206
  • 8
  • 33
  • 42
  • 2
    How many of such `enum`s do you expect to write in your lifetime? – Holger Oct 07 '15 at 12:49
  • I work on a project where many enums are to be introduced with much more than three values inside. Its a real time waste to code all this stuff by hand. – George Oct 07 '15 at 12:51
  • 1
    For the love of god, use universal color codes. It will make your life as well as the developer/s, who will come after you, a bit easier. this way, you don't deal with this enum thing. – We are Borg Oct 07 '15 at 12:55
  • @WeareBorg Totally agreed :) – George Oct 07 '15 at 13:42

2 Answers2

2

Instead of repeating the code in every enum, you may implement a central utility method for all kinds of enums with an int value. This is possible since enums can implement interfaces, hence you can define a uniform way of accessing the int value:

public interface IntValued {
    int intValue();
}
/** get the particular {@link IntValued} {@code enum} constant. */
public static <T extends Enum<T>&IntValued> T get(Class<T> type, int value) {
    return type.cast(GET.get(type).apply(value));
}
private static ClassValue<IntFunction<Enum>> GET = new ClassValue<IntFunction<Enum>>() {
    protected IntFunction<Enum> computeValue(Class<?> type) {
        return prepare(type);
    }
};
// invoked only once per enum type
static IntFunction<Enum> prepare(Class<?> type) {
    Enum[] values=type.asSubclass(Enum.class).getEnumConstants();
    if(values.length==0) return i -> null;
    IntSummaryStatistics s=Arrays.stream(values)
        .mapToInt(o -> ((IntValued)o).intValue())
        .summaryStatistics();
    int min=s.getMin(), max=s.getMax();
    if((max-min+1)<s.getCount()*2) {
        Enum[] linear=new Enum[max-min+1];
        for(Enum e: values) linear[((IntValued)e).intValue()-min]=e;
        return i -> i<min||i>max? null: linear[i-min];
    }
    Map<Integer, Enum> map = Arrays.stream(values).collect(
        Collectors.toMap(o -> ((IntValued)o).intValue(), Function.identity()));
    return map::get;
}

This uses ClassValue, a JRE provided way of associating custom meta data to a class. ClassValue takes care of invoking the initialization code only once and still allows garbage collection/ class unloading. The code above dynamically determines which lookup structure to use. If the int values of a particular enum type are dense, it uses an array, otherwise it resorts to a Map.

You can use it as follows:

public enum Color implements IntValued {
    RED(10),
    GREEN(22),
    BLUE(33);

    private int value;

    Color(int value) {
      this.value = value;
    }

    public int intValue() {
      return value;
    }
}

 

Color color=get(Color.class, 22);
System.out.println(color);

Note that this solution, unlike generated switch statements, does not break if these int values are changed.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Nice! I believe it was you that pointed out to me some time ago that `Class` has a `cast` method. `return type.cast(GET.get(type).apply(value));` would avoid an *evil* cast. – OldCurmudgeon Oct 07 '15 at 14:16
  • @OldCurmudgeon: you are right; here it backfires that Netbeans does not generate all kinds of unchecked warnings in its default configuration. – Holger Oct 07 '15 at 14:19
  • This seems unnecessarily complex. Why not just a static method with a signature like `public static Map mapByValue(Collection values)`, whose body is your second-to-last statement? It doesn't even need to be specific to enum types. – VGR Oct 07 '15 at 15:09
  • @VGR: The whole point is that this code does *not* populate a map each time you call the `get` method. It does so at most one time for a concrete `enum`, but, as already explained, may even use a faster array-based accessor, if the `int` values are dense. Each subsequent invocation will reuse that accessor function created on the first call. The goal was to be as fast as the `switch` statement of the question. – Holger Oct 07 '15 at 15:18
  • Couldn't the Map be a lazily populated, unmodifiable singleton? – VGR Oct 07 '15 at 15:20
  • @VGR: it seems you still don’t understand that this method does *not* use a map in all cases. There are three different result functions and, since you can’t implement a map via lambda expression, enforcing it to return a `Map` would require to create additional classes for that. Besides that, you still need a map instance per `enum` and thus have implement a way to cache these results and that’s what this solution is about. Except that it uses an `IntFunction` instead of a `Map` but replacing `IntFunction` with `Map` would not make anything simpler… – Holger Oct 07 '15 at 15:26
  • I got that, I just don't understand the need for so much optimization. What's wrong with a Map for every class? How many enum types could there possibly be? Even a thousand small Maps doesn't seem like a penalty to me. – VGR Oct 07 '15 at 15:28
  • 1
    @VGR: that’s the premise of the question. If you look at the comments there, I already asked, how much of these `enum` types are expected. This solution is for the OP’s assumption that the number is so large that auto-generated code per `enum` would be reasonable. Thus, I provided a solution which removes the need to add code to each `enum` type but is on par with the original `switch` solution’s performance (`tableswitch` for dense numbers and `lookupswitch` for the others). There wouldn’t be much problems with a map per type, but this solution is also to avoid a map *per invocation*. – Holger Oct 07 '15 at 15:33
0

If you are using Java 8 you can make use of default methods in interface to add functionality to any class - even enums.

Take a look here where I add a reverse-lookup to an enum just by making it implement ReverseLookup.

Community
  • 1
  • 1
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213