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: ,