Bootstrapping static fields within enums

In my earlier post on enums in Java 5, we have seen that static fields cannot be accessed within the enum constructor. With this restriction we could run into few initialization problems discussed below.

The other day Rajiv found it difficult to initialize a static cache during enum bootstrap. The following is his enum:

public enum Tag {
    KEYWORDS(2, 25, true),
    DATE_CREATED(2, 55, false),
    HEADLINE(2, 105, false),
    …
    private int recordNo;
    private int datasetNo;
    private int isRepeat;

    private Tag(int recordNo, int datasetNo, int isRepeat) {
        this.recordNo = recordNo;
        this.datasetNo = datasetNo;
        this.isRepeat = isRepeat;
    }
    …
    public static Tag getTag(int recordNo, int datasetNo) {
        for(Tag tag : Tag.values()) { //$REVIEW$ optimize
            if(tag.getRecordNo() == recordNo && tag.getDatasetNo() == datasetNo){
                return tag;
            }
        }
    }
}

Rajiv says, “Deeps, check my implementation of getTag(int recordNo, int datasetNo). I would have liked to make a static map of (recordNo<<8 + datasetNo) vs Tag to make this method fast. Unfortunately, I cannot access that static map from the constructor.”

So, he would like to have a static cache of enums for easy retrieval based on their properties. But, the concern is where do we initialize this cache/map? In the discussion following the earlier post, we discussed that “enums are initialized before any static initializers are run”. Bang! We can initialize this cache in a static block as shown below (actually derived from Neal Gafter’s discussion on forum).

public enum Tag {
    KEYWORDS(2, 25, true),
    DATE_CREATED(2, 55, false),
    HEADLINE(2, 105, false),
    …
    private static Map<integer, tag=""> cache;
    static {
        cache = new HashMap<integer, tag="">();
        for(Tag tag : values()) {
            int key = tag.getRecordNo()<< 8 + tag.getDatasetNo();
            cache.put(key, tag);
        }
    }
    …
    public static Tag getTag(int recordNo, int datasetNo) {
        int key = recordNo<<8 + datasetNo;
        return cache.get(key);
    }
}</integer,></integer,>

Enum constants are already constructed, by the time static block initializer is run. This is a ridiculous pattern to follow though, when you need to boot strap your static fields within enums.

Type Safe Enumerations in Java 5.0

The new enum construct in Java 5.0 is used to define enumerations. Enumerations are not new to programming languages, they existed in C, C++, SmallTalk etc. In Java 5.0 they had to be introduced to overcome safety and ease of use concerns in using the earlier enumeration approaches. Let us take a look at how we used to define enumeration constants prior to Java 5.0.

Type Safety concern for enumerations

In programming sense, enumerations can be treated as just a sequence of integers. Let us define an enumeration that represents a sample of Colors (RED, BLUE, YELLOW, GREEN, ORANGE & PURPLE).

public interface Colors {
    final int RED=1, BLUE=2, YELLOW=3, GREEN=4, ORANGE=5, PURPLE=6;
}

public interface Shape {
    void applyColor(int color);
    ...
}

Shape box = new Rectangle();//Rectangle implements Shape
box.applyColor(Colors.RED);

Whereever we need a color, we use the constants defined in Colors. But, what if an integer that is not defined for any of the constants is passed to the applyColor() method? Say box.applyColor(8), It fails. Shape.applyColor() method takes any integer, there is no "Type Safety".

So, it is not true to say enumerations are a sequence of integers. Enumerations should be treated as a separate type. Let us re-define the enumeration to take care of Type Safety.

Enumerated Pattern before Java 5

Prior to Java 5 release, enumerations were defined using the Enumerated pattern described in the book, Effective Java. Enumerated pattern takes care of type safety.

public final class Color
{
    public static final Color RED = new Color(1);
    public static final Color BLUE = new Color(2);

    private int color;    //ensures singleton and no instantiations

    private Color(int color) { this.color = color; }
    public int getColor() { return color; }

    //overridden to give meaningful values
    public String toString() {
        switch (getColor()) {
            case 1: return 'RED'; break;
            ...
        }
    }

    public boolean equals(Object obj) { return this == obj;}
}

public interface Shape {
    //The method takes a Color type, not integer
    void applyColor(Color color);
}

This enumerated pattern ensures that the Color type is Singleton and provides type safe enumerated constants RED, BLUE, YELLOW, GREEN etc. The toString() method is overriden to provide meaningful information when the constants are printed and the equals() method checks the reference equality of enumerated types, as these are singleton instances. Is this all enough to define a complete enumerated type? No.

Disadvantages of the above enumerated pattern

The enumerated pattern approach has the following drawbacks:

  • Enumerated types are not Serializable and Comparable by default. In case, if we make them Serializable,
    Serialization of Color creates a new instance of the same Color. The equals() method fails to identify the equality of a serialized and a non-serialized Color. (Ofcourse, we can fix this by custom serialization of enumerated types. But, this leads to writing more boiler plate code for each enumerated type)
  • Color instances cannot be directly used in the switch-case statements, instead you need to use the color value
  • If a new color is added between existing constants or if the order of Colors is changed, clients must be
    recompiled. (a.k.a Brittleness)
  • No support for iterating over the defined enumerated constants

Java 5 enum

enum provides a cleaner syntax to define enumerations in Java. And also overcomes all the drawbacks set by the earlier Enumerated pattern.

  • enums are type safe
  • enums provide default implementation for toString(), equals(), hashCode() methods (Umm.. no extra work)
  • enums are Comparable and Serializable by default
  • enums are allowed to be used in switch-case statements
  • enums are allowed to implement interfaces
  • Adding new enum constants, does not require re-compilation of the client code
  • A static values() method is provided that returns an array of defined enumerated constants

Let us define the Color enumeration using Java 5 enum construct.

public enum Color {
    RED, BLUE, YELLOW, GREEN, ORANGE, PURPLE
}

The enumerated types RED, BLUE, YELLOW etc. are of type Color. java.lang.Enum is the supertype of all enums and the Color enum implicitly extends java.lang.Enum. java.lang.Enum provides implementation for equals(), toString(), and hashCode() methods and it implements Serializable and Comparable interfaces. Enums are full-fledged java classes and can have arbitrary methods and fields. Enums can implement interfaces. Enums can have constructors and they take arguments in the declaration as shown below.

public enum Color {
    RED(625, 740),
    ORANGE(590, 625),
    YELLOW(565, 590),
    ...

    //Electro-magnetic Spectrum wavelength in nm
    int startWavelength;
    int endWavelength;

    Color(start, end) {
        this.startWavelength = start;
        this.endWavelength = end;
    }

    public int getStartWavelength() { return startWavelength; }
    public int getEndWavelength() { return endWavelength; }

    public static void main(String[] args) {
        System.out.println("Red color wavelength range = "
            + RED.getStartWavelength()+" ~ "+RED.getEndWavelength());
    }
}

If the Enumerated types have any attributes, a constructor is used to associate these attributes and the Getter methods expose these attributes.

Constant specific methods

If the implementation of methods vary for each Constant, then you can implement the methods specific to a constant.

public enum Color {
    RED { public Color complimentary() { return GREEN; }},
    BLUE { public Color complimentary() { return ORANGE; }},
    YELLOW { public Color complimentary() { return PURPLE; }},
    ...

    public abstract Color complimentary();
}

Each color has its own complementary color and to implement this we can use constant specific methods to return the complementary color. The method complementary() has to be either defined as an abstract method within Color enum or could be a method in one of the implemented interfaces.

Restrictions on enum types

Though enum types are full-fledged classes, the following are not allowed with enums:

  • enums cannot be subclassed
  • enums cannot have public constructor
  • enums are not allowed to be instantiated (using new)
  • enums cannot be cloned

All of these are enforced during compile-time.

Serialization of enums

Serialization mechanism ensures that new enum instances are not created on serialization and de-serialization (ensuring singleton behaviour). In a distributed environment when an enum is serialized from one JVM to another JVM, enum's state is not serialized. Hence, it is not encouraged to have state with enums which is updated by the applications. You can only have constant attributes that are initialized by the constructor.

Collections built for enums

java.util.EnumMap and java.util.EnumSet are the collections added for use with enum types. They provide a compact and efficient implementation for enum types. These collections are not synchronized and when used in multi-threaded environment, applications should take care of synchronizing the operations on the collection. EnumMap and EnumSet are homogenous collections of the same enum type. They cannot be used to operate on different enum types at the same time.

public enum Color {
    RED, BLUE, YELLOW, GREEN, ORANGE, PURPLE;

    public static EnumSet getPrimaryColors() {
        return EnumSet.of(RED, BLUE, YELLOW);
    }

    public static EnumSet getSecondaryColors() {
        return EnumSet.complementOf(getPrimaryColors());
    }

    public static void main(String[] args) {
        System.out.println("Primary Colors: "+Color.getPrimaryColors());
        System.out.println("Secondary Colors: "+Color.getSecondaryColors());
        System.out.println("Universe: "+EnumSet.allOf(Color.class));
    }
}

Few gotchas about enums

However, there are few things which could probably be improved in the coming versions of Java.

  • Currently null can be passed as a method parameter or assigned where an enum is expected. A compile-time verification for null enum assignment would probably avoid all those extra null checks that we need to incorporate in our code.
  • Static fields within enums cannot be accessed in its constructor. But, static methods are allowed to be invoked. The compiler enforces verification only for static fields and not methods.
  • However, enum constructors are invoked before static initialization is performed and hence the static fields would not be initialized. So, if you invoke static methods from the constructor which inturn access static fields, they would all be un-initialized.

Enum syntax

public enum MyEnum {
    CONST1 () {
        ;
    },
    CONST2 (...) {...},
    ...;
    //enum constructor [optional]
    MyEnum () {
        //static fields within enum cannot be accessed
    }
    ; [optional]
}