views:

169

answers:

4

I will often need to translate from the string code into its Enum type. See snippet below. I think this is highly inefficient. How can I improve the method deref so that:

  • It will be threadsafe.
  • It is faster.

Code:

public enum SigninErrorCodes
{
    InvalidUser("a0"), InvalidPassword("b5"), NoServerResponse("s2");

    SigninErrorCodes( String code ) { _code = code; }

    public String code() { return _code; }

    public static SigninErrorCodes deref( String code )
    {
      SigninErrorCodes[] verbs = values();
      Map<String, SigninErrorCodes> m = new HashMap<String,SigninErrorCodes>(3);
      for( int i=0; i<verbs.length; i++ )
        m.put( verbs[i].code(), verbs[i] );

      return m.get( code );
    }

    private final String _code; 
}
+2  A: 

Enum has a static method called [valueOf][1] to handle this. You would do:

 public static SigninErrorCodes deref( String code ){
      try {
          return Enum.valueOf(SignInErrorCodes.class, code);
      } catch (IllegalArgumentException e) {
          return null;
      } catch (NullPointerException e) {
          return null;
      }
 }

Note that the error handling here attempts to imitate the same results as your original method. That may not be in fact how you want to handle an invalid code, or you may be assuming there will be no invalid codes at runtime, so you would just let the exceptions propagate.

The above applies if you can name your enums according to the code (something that would be very desirable in this case, it would seem).

If that is not possible for some reason, then you could quite simply use a Hashtable (whcih is syncronized) as your map implementation and keep that in a static field, and populate it in the constructor of the enum (map.put(_code, this.getClass());).

If the excessive syncronization is too much performance-wise, then look at storing the map in a ThreadLocal.

[1]: http://java.sun.com/javase/6/docs/api/java/lang/Enum.html#valueOf(java.lang.Class, java.lang.String)

Yishai
You can also just call valueOf as a static member: SigninErrorCodes.valueOf(code); However, depending on the program, the cost of an exception for each missed call may be high.
Marcus Downing
+1  A: 

One thing that will make it faster is keep the map in a static variable, so you don't have to recompute it each time; dunno about the threadsafe part.

public enum SigninErrorCodes {
    InvalidUser("a0"), InvalidPassword("b5"), NoServerResponse("s2");

    private static Map<String, SigninErrorCodes> m = new HashMap<String,SigninErrorCodes>(3);

    SigninErrorCodes( String code ) { _code = code; m.put(code, this); }

    public String code() { return _code; }

    public static SigninErrorCodes deref( String code )
    {
      return m.get( code );
    }
}

If the code strings are valid identifiers, you can just use those as the enumerations values directly:

public enum SigninErrorCodes { a0, b5, s2; }

// and then when you need to look it up, use:
SigninErrorCodes.valueOf("b5");
newacct
+1  A: 

If the list of possible values really is that small, then write the method by hand. The performance benefit of a hashtable isn't felt in comparison to a mere 3 string comparisons.

But if, as I suspect, the number of values is much bigger than you've written then a map makes sense, in which case you should cache the map when the enumeration is created.

public enum SigninErrorCodes {
  InvalidUser("a0"), InvalidPassword("b5"), NoServerResponse("s2");

  private final String _code;
  SigninErrorCodes( String code ) { _code = code; }
  public String code() { return _code; }

  private static final Map<String, SigninErrorCodes> m;

  static {
    SigninErrorCodes[] verbs = values();
    m = new HashMap<String,SigninErrorCodes>(verbs.length * 2);
    for( int i=0; i<verbs.length; i++ )
      m.put( verbs[i].code(), verbs[i] );
  }


  public static SigninErrorCodes deref( String code ) {
    return m.get( code );
  }
}

Note that the best size to give a hash map isn't the number of elements, but about twice that. A lower number results in key conflicts.

Marcus Downing
Marcus, that is interesting. Can you point me to a source that says HashMaps should be initialized to double their anticipated keys?
Yishai
if you look at the JavaDoc for HashMap, pay close attention to the initialCapacity: http://java.sun.com/javase/6/docs/api/java/util/HashMap.html. 2 times might be stretching it, but it doesnt hurt here.
akf
I believe 0.70-0.75 is the usual range for load factor (see wikipedia, IIRC). Low load factors do make things slower through cache misses.
Tom Hawtin - tackline
A load factor of 0.75 (the default) means the initial capacity should be at least 1.5 times the actual size to avoid rebuilding the table. A little extra above that doesn't hurt. "about twice" was just a guess.
Marcus Downing
+2  A: 

Use the Reversible Enum Pattern

mtpettyp