Skip to content

Java enums – choice between fields, abstract methods, and class level map

I have written a Java enum where the values have various attributes. These attributes could be stored in any of the following ways:

Using fields:

enum Eenum {
  V1(p1),
  V2(p2);

  private final A attr;

  public A attr() { return attr; }

  private Eenum(A attr) {
    this.attr = attr;
  }
}

Using abstract methods:

enum Eenum {
  V1 {
    public A attr() { return p1; }
  },

  V2 {
    public A attr() { return p2; }
  }

  public abstract A attr();
}

Using class level map:

enum Eenum {
  V1,
  V2;

  public A attr() { return attrs.get(this); }

  private static final Map<Eenum, A> attrs;

  static {
    ImmutableMap.Builder<Eenum, A> builder = ImmutableMap.builder();
    builder.put(V1, p1);
    builder.put(V2, p2);
    attrs = builder.build();
  }
}

How should I decide when to prefer which?

Thanks!

Answer

(I am answering my own question so that I can share some things I learned while trying out things.)

Here are the questions you should ask to come at a decision for your specific case:

1: Do the attribute values involve forward references?

Sometimes V1‘s attribute may need a reference to V2 and vice versa. This is not a rare case. If you are dealing with such an enum, approach 1 simply would not work. The compiler will (rightly) complain about illegal forward references. Any of the other two approaches can be used.

Now, if the attribute value is expensive to compute and a constant, you’d want that it’s computed only once. With approach 2, you’d have to introduce local variables per enum value, and cache results there. This is verbose but will give you better performance. With approach 3, the results are anyway computed only once, and so don’t have to do any extra work. This is more readable but somewhat less performant than approach 2. Design between these as per the specific trade offs warranted in your case.

2: Do I need to cache results?

Refer to the second paragraph of previous bullet.

If there are no forward references, you can use approach 1 too. But if the computation involved in calculation of attributes is complex, you are better off with one of the other two approaches.

3: Are the attributes relevant for all of the enum values?

If not, then quite logically, you should be using a Map here. That is, approach 3.

4: Are there any default values for some attributes for some enum values?

If so, you can use all three approaches, and they all offer different set of trade-offs.

With approach 1: You would define an auxiliary constructor that initializes the attribute to the default value. If there are multiple such attributes, this might not be a feasible approach.

With approach 2: This will actually be like “fourth” approach Peter Lawrey suggested above. You will have a method returning the default value in enum‘s main body. And some enum values will override this method to return a different value. This is, again, quite verbose.

With approach 3: Just less efficient. Good in every other way.