ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Enums in Java (One More Time)
Pages: 1, 2, 3

Java Alternatives for Enumerated Types

Java does not directly support user-defined enumerated types, but two general approaches have evolved as alternatives.



The first alternative, which I will refer to as the enums-as-integer-constants alternative, uses integer constants. For example, a Java program might contain:

public static final int SUNDAY    = 0;
public static final int MONDAY    = 1;
public static final int TUESDAY   = 2;
public static final int WEDNESDAY = 3;
public static final int THURSDAY  = 4;
public static final int FRIDAY    = 5;
public static final int SATURDAY  = 6;

The integer constants allow us to work in Java using symbolic names instead of raw constant values, making the source code much more readable and maintainable. The constants can be declared within a larger class with other fields and methods, such as a Date class, or they can be collected in a separate class, such as Day. With the separate class approach, constants will need to be qualified, as in "Day.MONDAY." As a variation of the separate class approach, one could use a Java interface. Then, other classes needing to access the constants could "implement" the interface and access the constants by their simple name; i.e., "MONDAY." Personally, I dislike the use of interfaces for this purpose.

This enums-as-integer-constants alternative clearly satisfies criteria 3 (language integration) and 4 (efficiency) since the enumerated values are implemented as ints, but it fails on the other two. It is too verbose and certainly isn't type-safe, the more serious failure. Although I have used this approach in the past, I do not consider it to be a viable alternative to actually having enums built into the language.

As outlined in publications by Eric Armstrong, Joshua Bloch, Glen McCluskey, and others (see Resources), the second alternative, which I will refer to as the enums-as-objects alternative, creates a distinct class for the enumerated type and uses public objects of that class for the enumerated values. The class definition for Day would look something like the following:

import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.io.Serializable;
import java.io.InvalidObjectException;

public final class Day implements Comparable, Serializable {
    private static int size    = 0;
    private static int nextOrd = 0;
    private static Map nameMap = new HashMap(10);

    private static Day first   = null;
    private static Day last    = null;

    private final int    ord;
    private final String label;

    private Day prev;
    private Day next;

    public static final Day SUNDAY    = new Day("SUNDAY");
    public static final Day MONDAY    = new Day("MONDAY");
    public static final Day TUESDAY   = new Day("TUESDAY");
    public static final Day WEDNESDAY = new Day("WEDNESDAY");
    public static final Day THURSDAY  = new Day("THURSDAY");
    public static final Day FRIDAY    = new Day("FRIDAY");
    public static final Day SATURDAY  = new Day("SATURDAY");

    /**
     * Constructs a new Day with its label.
     * (Uses default value for ord.)
     */
    private Day(String label) {
        this(label, nextOrd);
    }

    /**
     * Constructs a new Day with its label and ord value.
     */
    private Day(String label, int ord) {
        this.label = label;
        this.ord   = ord;

        ++size;
        nextOrd = ord + 1;

        nameMap.put(label, this);

        if (first == null)
            first = this;

        if (last != null) {
            this.prev = last;
            last.next = this;
        }

        last = this;
    }

    /**
     * Compares two Day objects based on their ordinal values.
     * Satisfies requirements of interface java.lang.Comparable.
     */
    public int compareTo(Object obj) {
        return ord - ((Day)obj).ord;
    }

    /**
     * Compares two Day objects for equality.  Returns true
     * only if the specified Day is equal to this one.
     */
    public boolean equals(Object obj) {
        return super.equals(obj);
    }

    /**
     * Returns a hash code value for this Day.
     */
    public int hashCode() {
        return super.hashCode();
    }

    /**
     * Resolves deserialized Day objects.
     * @throws InvalidObjectException if deserialization fails.
     */
    private Object readResolve() throws InvalidObjectException {
        Day d = get(label);

        if (d != null)
            return d;
        else {
            String msg = "invalid deserialized object:  label = ";
            throw new InvalidObjectException(msg + label);
        }
    }

    /**
     * Returns Day with the specified label.
     * Returns null if not found.
     */
    public static Day get(String label) {
        return (Day) nameMap.get(label);
    }

    /**
     * Returns the label for this Day.
     */
    public String toString() {
        return label;
    }

    /**
     * Always throws CloneNotSupportedException;  guarantees that
     * Day objects are never cloned.
     *
     * @return (never returns)
     */
    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

    /**
     * Returns an iterator over all Day objects in declared order.
     */
    public static Iterator iterator() {
        // anonymous inner class
        return new Iterator()
        {
            private Day current = first;

            public boolean hasNext() {
                return current != null;
            }

            public Object next() {
                Day d   = current;
                current = current.next();
                return d;
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }

    /**
     * Returns the ordinal value of this Day.
     */
    public int ord() {
        return this.ord;
    }

    /**
     * Returns the number of declared Day objects.
     */
    public static int size() {
        return size;
    }

    /**
     * Returns the first declared Day.
     */
    public static Day first() {
        return first;
    }

    /**
     * Returns the last declared Day.
     */
    public static Day last() {
        return last;
    }

    /**
     * Returns the previous Day before this one in declared order.
     * Returns null for the first declared Day.
     */
    public Day prev() {
        return this.prev;
    }

    /**
     * Returns the next Day after this one in declared order.
     * Returns null for the last declared Day.
     */
    public Day next() {
        return this.next;
    }
}

The enumerated values are declared as public static objects in the class. Additionally, the class contains private constructors, an Iterator to step over all of the values, common Java methods such as toString(), equals(), and compareTo(), and methods similar to those in other languages that are designed to make the class more usable, such as ord(), prev(), next(), first(), and last(). Additional examples are available in the articles listed in the Resources section below and in the download files for this article.

This enums-as-objects alternative has the very desirable characteristics of type safety and runtime efficiency (criteria 1 and 4 above), but it fails miserably on the other two criteria. In particular, it is much wordier than the enums-as-integer-constants alternative, and therefore less likely to be used by most programmers. Also, it does not permit enums to be used as array indices or as case alternatives for a switch statement, thereby decreasing its utility. In general, I prefer this second (enums-as-objects) alternative, but in practice, I have used it less often.

In summary, neither approach provides a satisfactory alternative to having enums built into the language. Now, I am not in a position to modify the Java language beyond voting for proposed language changes, so I can't do anything about the integration of enums-as-objects with the rest of Java, but I can remove the burden imposed by the effort to create the class and objects necessary for the second alternative.

Pages: 1, 2, 3

Next Pagearrow