I seem to have faced this problem many times and I wanted to ask the community whether I am just barking up the wrong tree. Basically my question can be distilled down to this: if I have an enum (in Java) for which the values are important, should I be using an enum at all or is there a better way, and if I do use an enum then what is the best way to reverse the lookup?

Here's an example. Suppose I want to create a bean representing a specific month and year. I might create something like the following:

Here I'm storing my month as a separate class called Month, so that it is type-safe. If I just put int, then anyone could pass in 13 or 5,643 or -100 as a number, and there would be no way to check for that at compile-time. I'm restricting them to put a month which I'll implement as an enum:

Fairly straightforward, but what happens if I want to read these values from the database as well as writing them? I could just implement a static function using a case statement within the enum that takes an int and returns the respective Month object. But this means if I changed anything, then I would have to change this function as well as the constructor arguments - change in two places. So here's what I've been doing. First off I created a reversible map class as follows:

Now this does everything I want it to, but it still looks wrong. People have suggested to me "if the enumeration has a meaningful internal value, you should be using constants instead". However, I don't know how that approach would give me the type-safety I am looking for. The way I've developed does seem overly complicated though. Is there some standard way to do this kind of thing?

PS: I know that the likelihood of the government adding a new month is...fairly unlikely, but think of the bigger picture - there are plenty of uses for enums.

4 Answers
4

This is a very common pattern, and it's fine for enums... but it can be implemented more simply. There's no need for a "reversible map" - the version which takes the month number in the constructor is better for going from Month to int. But going the other way isn't too hard either:

In the specific case of numbers which you know will go from 1 to N, you could simply use an array - either taking Month.values()[value - 1] or caching the return value of Month.values() to prevent creating a new array on every call. (And as cletus says, getMonthNum could just return ordinal() + 1.)

However, it's worth being aware of the above pattern in the more general case where the values may be out of order, or sparsely distributed.

It's important to note that the static initializer is executed after all the enum values are created. It would be nice to just write

numberToMonthMap.put(monthNum, this);

in the constructor and add a static variable initializer for numberToMonthMap, but that doesn't work - you'd get a NullReferenceException immediately, because you'd be trying to put the value into a map which didn't exist yet :(

In this particular case, it doesn't add much value - but I've used this pattern over and over. (It's a pain it's hard to extract out into a helper class.)
–
Jon SkeetOct 17 '09 at 19:35

Thanks everyone for your comments. Jon - I liked your solution best and I hadn't thought of using a generalist loop to populate the map. The example I gave is quite specific in that the internal representations are integers and they are in the exact order given. However, if I was using characters, or numbers with gaps between them etc, then I am not able to use the ordinal() solution, so I have to use something similar to what Jon provides. And I do agree with him that it's tough there's no way to create a base class with this functionality built-in. But I'll create a template in my IDE :)
–
KidburlaOct 19 '09 at 13:15

I've been bit a couple times with this. One notable one was where I had a communication protocol and had mapped message codes to the ordinal. It didn't break, but when I had codes like adduser at position 6 and remuser next to it at 7, and then I had to add a new one like moduser at position 20. They really should have been grouped together logically, but I couldn't do that because it would have shifted many of the other code numbers. I know months don't change often, but a lot of real world things do (regardless of how many times business users say they won't!!)
–
corsiKaSep 16 '13 at 22:04

As for how to store this in a database, it depends on what persistence framework (if any) you're using. JPA/Hibernate have the option of mapping enum values by either number (ordinal) or name. Months are something you can probably take as non-changing so just use the ordinal. To get a specific value:

One downside of calling Month.values()[ordinalNumber] is that it needs to create a copy of the array each time. It would be nice if Java would expose an immutable list property like values() which could just return a reference to the same list on every call.
–
Jon SkeetOct 17 '09 at 19:17

May be so but to develop a solution for that "problem"--unless you're doing that millions of times--is pointless micro-optimization.
–
cletusOct 17 '09 at 19:30

You shouldn't use ordinal() for this kind of thing, for the sample with months it would work (because it will not be extended) but one of the good things with enums in java is that they are designed to be possible to extend without breaking things. If you start relying on ordinal() things will break if you add some value in the middle.

I would do it like Jon Skeet suggests (he wrote it while I was writing this) but for cases where the internal number representation is in a well defined range of say 0 to 20 (or something) I would probably not use a HashMap and introduce autoboxing of the int but rather use an ordinary array (like Month[12]) but both are fine (Jon later changed his post to include this suggestion).

Edit: For the few enums where there is a natural order (like sorted months) ordinal() is probably safe to use. The problems you risk running into if you persist it will appear for things where someone might change the order of the enum. Like if:
the "enum { MALE, FEMALE }" becomes an "enum {UNKNOWN, FEMALE, MALE}" when someone extends the program in the future not knowing that you rely on ordinal.

@cletus: that's subjective and I disagree. Was that the reason for the downvote? There is a rather complete explanation of why you should never rely on ordinal if you pickup a copy of Effective Java (or read item 31 here: books.google.se/… )
–
FredrikOct 17 '09 at 19:33

@Fredrik: I think someone's downvoted all three answers, although I don't know why.
–
Jon SkeetOct 17 '09 at 19:36