Can enumerations and web-apps coexist?

I’ve had this entry stagged for a week or so, waiting to do some more research and checking, but since a similar topic is discussed here and here, I’ll throw in my thoughs –

Enumerations made their ways into Java SE 5 (JDK 1.5) and you’d think
that using enum is now the only way to go,
right? It’s got to be better than the Type-safe enum defined
by Josh Bloch in Effective Java.

public enum SubscriptionType {
    MAGAZINE, JOURNAL, NEWS_PAPER, OTHER;
}

On the other hand, everything a user enters in a web-app is a String.
In particular HttpServletRequest.getParameter()
returns a String. In most cases, dropdown
choices should be held in an enum (or some
database table). Going from the Enum
value to a String is as easy as overriding
the toString() method with something like
this:
   
    public String toString() {
       
switch (this) {
           
case
MAGAZINE:      return
"Magazine";
           
case
JOURNAL:      
return "Journal";
           
case NEWS_PAPER:    return "NewsPaper";
           
case
OTHER:        
return "Other";
           
default:    throw new AssertionError(this);
        }
    }

Or using a constructor and an instance String
variable:

public enum SubscriptionType {
    MAGAZINE("Magazine"),
JOURNAL("Journal"), NEWS_PAPER("NewsPaper"), OTHER("Other");
   
    private String stringValue;
   
    // constructor
    SubscriptionType(String s) {
       
stringValue = s;
    }

    public String getValue() {
       
return this.stringValue;
    }

So, how do I go from a user-provided String
to a value defined in an Enum? The use of SubscriptionType.valueOf(String
s)
only works when s is the same
as the name of the value (MAGAZINE for
instance, which wouldn’t fly for I18N) and you can’t override valueOf(),
so it’s tempting to stay with the good old set of :

    public static final String
"Magazine";
    public static
final String "Journal";
    public static
final String "NewsPaper";
    public static
final String "Other";

But you could also build on top of the mapping established in the toString()
method and provide the reverse operation :

    public static
SubscriptionType matchTypeFromString (String s) {
       
for ( SubscriptionType t : values() ) {
           
if ( s.compareTo( t.toString() ) == 0 )
               
return t;
        }
       
// Couldn't match the provided String
       
throw new IllegalArgumentException ("Unknown Subscription type : "+s);
    }

– Such a method could be factored into an abstract class.
Of
course, this should ideally also be internationalized using ResourceBundles.
– In the case of a JSF application, this convertion logic should
probably be part of the custom converters’ getAsObject()
and getAsString() methods.
– Dropdown choices could use the index rather than the value, but are
enums ordered? Yes, use the ordinal() method.

The above is probably not the most elegant of best performing solution.
Any better one?
Does a tool out there have a refactoring for moving type-safe enums to
“real” enums? Not trivial I think.

Update: Eamonn went the extra mile and provided the generified method as a comment, together performance hints with generics caveats. Thanks!

Advertisements

Author: alexismp

Google Developer Relations in Paris.

2 thoughts on “Can enumerations and web-apps coexist?”

  1. I ran in the same troubles (or thoughs) you’re running last week, trying to hold some information of a robot targeting system in a Robocode robot, since each operation costs processing time, using enum switchs would improve processing time significally.

    After a small think time, I assumed that the best (not the easiest) aproach would be writting my own enum class, in a way like

    public TargetingOptions extends Enum {
    //stuff here
    }
    

    Unfortunatelly, I was lazy enough to avoid having a deeper look on that, and kept the common if-else aproach.

    Cheers, Juan

  2. You could write a general-purpose version of the matchTypeFromString method, something like this:

    public static <E extends Enum<E>> E stringToEnum(Class<E> c, String s) {
    for (E e : EnumSet.allOf(c)) {
    if (e.toString().equals(s))
    return e;
    }
    throw new IllegalArgumentException("Unknown enum string for " +
    c.getName() + ": " + s);
    }
    

    You could also improve its performance by remembering the mapping from strings back to enum constants, with a WeakHashMap<Class<E>, WeakReference<Map<String, E>>>, though you rapidly run into the limitations of generics doing this. (You can’t declare a field of the above type because fields can’t have type variables, so some casting is inevitable.) The idea is that the mapping from a given Enum class to its table of string-to-Enums would be constructed the first time you call stringToEnum for the class and remembered thereafter. The WeakHashMap and WeakReference are needed so that this map doesn’t prevent the Enum class from being garbage-collected. In addition to the performance advantage, this would allow you to detect early if any strings were ambiguous.

Comments are closed.