October 5, 2009

Java Enumerations (Sometimes) Considered Harmful

Tags: ,

In a recent coding project I fell foul of Java enumerations. They seem like a very good idea, and indeed they are when used properly. However, there is sometimes a tendency to overuse them – something unfortunately learnt from personal experience.

Intially Java had no inbuilt concept of enumerated types. Developers often found themselves inadequately simulating them by creating a bunch on integer constants. Proper enumerations were added to Java in JDK1.5 with the enum keyword and developers rejoiced (or at least I did). The following example from the Java Language Guide shows how they are used:

public enum Rank { DEUCE, THREE, FOUR, FIVE, SIX,
        SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE }

public enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES }

Actually, the above is just the basic usage pattern. Java enums can have data and behavior, as shown in the Planet example below (from the same guide). They can also implement abstract methods directly at the definition (see the guide) but they cannot be subclassed.

public enum Planet {
    MERCURY (3.303e+23, 2.4397e6),
    VENUS   (4.869e+24, 6.0518e6),
    EARTH   (5.976e+24, 6.37814e6),
    MARS    (6.421e+23, 3.3972e6),
    JUPITER (1.9e+27,   7.1492e7),
    SATURN  (5.688e+26, 6.0268e7),
    URANUS  (8.686e+25, 2.5559e7),
    NEPTUNE (1.024e+26, 2.4746e7),
    PLUTO   (1.27e+22,  1.137e6);

    private final double mass;   // in kilograms
    private final double radius; // in meters
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }
    public double mass()   { return mass; }
    public double radius() { return radius; }

    // universal gravitational constant  (m3 kg-1 s-2)
    public static final double G = 6.67300E-11;

    public double surfaceGravity() {
        return G * mass / (radius * radius);
    }
    public double surfaceWeight(double otherMass) {
        return otherMass * surfaceGravity();
    }
}

I think the first (Rank & suit) example is correct usage of enums – essentially treating them as related constant values. In my opinion, the second example is problematic. Here the Planet enum is acting similarly to a class with each instance being like an object, with data and associated behaviour. At the level of the example this may seem acceptable – not much is happening here. However, code rarely stays static. Requirements change and thus the code changes to handle different functionality. It is not hard to imagine more data being required for each Planet. Object-oriented classes are designed to help handle this change. Enumerations are not and there is no inheritance, nor is it possible to pass non static constructor arguments. As a result, as the functionality of an enum becomes more “object-like”, the code supporting it becomes increasingly messy.

The general rule is that linked constants should be enumerations, but anything more than that should probably be a class.

I have broken this rule myself and lived to regret it. A project I recently worked on aggregated a number of different financial markets and presented a single interface for downstream clients. This project used quite a few enumerations. For example, there were enums to define the market defined type of a financial instrument and another for its pricing method. These worked fine, they were mutually exclusive values read from the markets essentially as constants. There was also an enum for the markets themselves. At first this was fine, each market was treated the same. However, as the system expanded to new markets and extra functionality, differences between markets began to appear. More and more logic began to hang off the market enum. Some was data stored on the market enum. Most was determined by the environment and was set by configuration settings. This logic ended up in various special classes constructed by Factory classes switching on market. Indeed there was quite a bit of logic in the system that switched code path depending on market. The code would definitely have worked better had there been a Market class rather than enumeration. Refactoring all the code to use a class rather than a enumeration was a big job in an important working system.


comments powered by Disqus