Mutant World

Sunday, July 27, 2008

Static Fields in Enums

Enums in Java have a numeric, read-only, property called ordinal that is a strictly consecutive integer (starting from 0).

Sometimes, however, it is necessary to associate to an Enum another numeric property, let's call it code, that it may not be strictly consecutive and normally specifies something different than a sequence number. Furthermore, it may be necessary to be able to lookup the correct Enum from its code.

It seems totally trivial to write an Enum that has a static map from codes to Enum instances:

public enum Port
{
SSH(22), TELNET(23), HTTP(80), POP3(110), HTTPS(443);

// The static map from codes to Enum instances
private static final
Map<Integer, Port> ports = new HashMap<Integer, Port>();
private final int code;

private Port(int code)
{
this.code = code;
ports.put(code, this);
}

public int getCode()
{
return this.code;
}

// Lookup method that returns the Enum instance from the given code
public static Port from(int code)
{
return ports.get(code);
}
}

Note the method static from(int code) that returns an Enum instance from the given code.

Unfortunately, this will not compile. The compiler reports that it is illegal to access the static member ports from the constructor.

This makes sense when one imagines how an Enum is first translated into a class by the compiler. Roughly, there is a static initializer that creates the Ports instances, in this case SSH, TELNET, etc.; this static initializer is the first initializer run when the Ports class is referenced, and it is easy to see that when the static initializer runs, no other initializers have been run yet, and in particular the static Map ports has not been created yet.
See http://java.sun.com/docs/books/jls/third_edition/html/classes.html#301020 for further details.

Fortunately, there is an easy workaround: it's enough to use a different class to store the map from codes to Enum instances, and a private static inner class does the job:

public enum Port
{
SSH(22), TELNET(23), HTTP(80), POP3(110), HTTPS(443);

private final int code;

private Port(int code)
{
this.code = code;
Ports.ports.put(code, this);
}

public int getCode()
{
return this.code;
}

// Lookup method that returns the Enum instance from the given code
public static Port from(int code)
{
return Ports.ports.get(code);
}

private static class Ports
{
// The static map from codes to Enum instances
private static final
Map<Integer, Port> ports = new HashMap<Integer, Port>();
}
}

Labels: ,

3 Comments:

  • I believe that you can not access port because it is private within your enum, and you access it from the constructor of a class that extends Port. If you add the following method to your Port class and call it from the constructor, that would work too:
    static void AddToPort(int code, Port port)
    {
    ports.put(code, port);
    }

    By Blogger David Shay, at 28 July, 2008 14:50  

  • My mistake, it compiles but gives you a NullPointerException...

    By Blogger David Shay, at 28 July, 2008 14:54  

  • OK, it does not work because the constructor are called before anything else. If you use lazy initialization for your hashmap, it will work, but then your variable can not be final. Your solution is probably more elegant.

    By Blogger David Shay, at 28 July, 2008 14:57  

Post a Comment

<< Home