Transilvania JUG

Sponsors

17
Sep
2014
5

Ensuring the numeric values of Enum constants

Problem statement: you have an Enum and you want to ensure that the numeric values returned by ordinal are a certain way (perhaps this Enum is part of a communication protocol with an external system or perhaps it is written to a DB using an ORM).

One solution would be to write a unit test like the following:


@Test
public void testEnum() {
	Map<TestEnum, Integer> expectedValues = new EnumMap<TestEnum, Integer>(TestEnum.class);
	expectedValues.put(TestEnum.FOO, 0);
	expectedValues.put(TestEnum.BAR, 1);
	expectedValues.put(TestEnum.BAZ, 2);

	assertEquals(TestEnum.values().length, expectedValues.size());
	for (Map.Entry<TestEnum, Integer> entry: expectedValues.entrySet()) {
		assertEquals(entry.getKey().ordinal(), entry.getValue().intValue());
	}
}

Of course this presupposes that the unittests are run frequently to catch any possible errors. Alternatively we can implement our Enum as shown below, which will check the values during load time (and prevent the loading of the enum by the classloader – by virtue of throwing an exception during the initialization phase – if the values are incorrect):


public enum TestEnum {
	FOO(0), BAR(1), BAZ(2);

	private static class CounterHolder {
		static int COUNTER = 0;
	}

	TestEnum(int order) {
		if (order != CounterHolder.COUNTER) {
			throw new IllegalArgumentException("Expected "
					+ CounterHolder.COUNTER + " but was " + order);
		}
		CounterHolder.COUNTER++;
	}
}

5 Responses to Ensuring the numeric values of Enum constants

  1. Cristi Botiza says:

    ordinal() is an implementation detail and you shouldn’t rely on it too much.
    Quote from the javadoc:
    Returns the ordinal of this enumeration constant (its position in its enum declaration, where the initial constant is assigned an ordinal of zero). Most programmers will have no use for this method. It is designed for use by sophisticated enum-based data structures, such as EnumSet and EnumMap.

    I’d recommend using a custom field for this and exposing it’s value to any serialization protocol, ORM tool etc.

    e.g.
    public enum TestEnum {
    FOO(1), BAZ(10);
    private final int myValue;

    public TestEnum(int myValue) {
    this.myValue = myValue;
    }

    public int getValue() {
    return myValue;
    }
    }

    ORM tools allow you to customize the mappings, by default they will use ordinal() or name(). None is safe particularly if you modify the enum after some values are persisted. If you remove a literal, code will break. If you add a literal, existing code will work.
    Not sure I answered the question, hope this helps.

    • Thank you for the reply!

      I think ordinal is pretty well documented (as you mentioned yourself). The only problem is that one might reorder the entries and change the assigned ordinal without realizing that.

      Agreed that using a separate field is a better option, but sometimes it isn’t feasible.

  2. Claudiu says:

    Nice article. This is a very useful idea.
    Instead of the CounterHolder you could compare with order with ordinal() as it acts like a counter when the class is built.

Leave a Reply

Your email address will not be published. Required fields are marked *

df