1.16. Creating an Enum

Problem

You need an Enum, but you are not using the new Java 1.5 release.

Solution

If you are stuck with Java 1.4 or lower, you can use Commons Lang Enum, ValuedEnum, and EnumUtils to create an enum “type,” which approximates the new language feature. Example 1-10 extends Enum, providing three flavors: Flavor.CHOCOLATE, Flavor.VANILLA, and Flavor.STRAWBERRY.

Example 1-10. Defining a Flavor enumeration by extending Enum

import java.util.*;
import org.apache.commons.lang.enum.Enum;

public final class Flavor extends Enum {
    public static Flavor CHOCOLATE = new Flavor( "Chocolate" );
    public static Flavor VANILLA = new Flavor( "Vanilla" );
    public static Flavor STRAWBERRY = new Flavor( "Strawberry" );

    private Flavor(String name, int value) { super( name, value ); }

    public static Flavor getEnum(String flavor) {
        return (Flavor) getEnum(Flavor.class, flavor);
    }
 
    public static Map getEnumMap( ) {
        return getEnumMap(Flavor.class);
    }
 
    public static List getEnumList( ) {
        return getEnumList(Flavor.class);
    }
 
    public static Iterator iterator( ) {
        return iterator(Flavor.class);
    }
}

The key to an Enum is the private constructor; this guarantees that there are only three instances of Flavor in any virtual machine. This extension of Enum provides a number of useful utilities to get a List or Map of available Flavor instances:

// Using Flavor to create an ice cream cone
IceCreamCone cone = new IceCreamCone( );
cone.setScoops( 5 );
cone.setFlavor( Flavor.VANILLA );
cone.addTopping( "sprinkles" );

List flavors = Flavor.getEnumList( );
System.out.println( ArrayUtils.toString( flavors.toString( ) ) );
// prints "{ Chocolate, Vanilla, Strawberry }"

Discussion

Assume that you are writing a system to classify choral singers by voice type: soprano, alto, tenor, and bass. Your database has a table dbo.lu_VoicePart, which contains a part name and a numeric part code. This voice part is a coded type; it needs both a name and a code, and a straightforward design is achieved by extending ValuedEnum . ValuedEnum is an Enum that keeps track of a numeric code for each instance of the enumeration. PartEnum extends ValuedEnum to store static instances of the four possible voice parts: soprano, alto, tenor, and bass. (See Example 1-11.)

Example 1-11. Extending ValuedEnum to create an enum

import java.util.*;
import org.apache.commons.lang.enum.ValuedEnum;

public final class PartEnum extends ValuedEnum {
    public static int  SOPRANO_VAL = 1;
    public static int  ALTO_VAL = 2;
    public static int  TENOR_VAL = 3;
    public static int  BASS_VAL = 4;

    public static PartEnum SOPRANO = new PartsEnum( "Soprano", SOPRANO_VAL );
    public static PartEnum ALTO = new PartsEnum( "Alto", ALTO_VAL );
    public static PartEnum TENOR = new PartsEnum( "Tenor", TENOR_VAL );
    public static PartEnum BASS = new PartsEnum( "Bass", BASS_VAL );

    private PartsEnum(String name, int value) { super( name, value ); }

    public static PartEnum getEnum(String part) {
        return (PartEnum) getEnum(PartEnum.class, part);
    }

    public static PartEnum getEnum(int part) {
        return (PartEnum) getEnum(PartEnum.class, part);
    }

    public static Map getEnumMap( ) {
        return getEnumMap(PartEnum.class);
    }

    public static List getEnumList( ) {
        return getEnumList(PartEnum.class);
    }

    public static Iterator iterator( ) {
        return iterator(PartEnum.class);
    }
}

This class extends ValuedEnum and has a private constructor. This guarantees that the only instances of this enumeration will be defined in PartEnum; four instances of PartEnum are created as public static final constants, and a name is assigned to every part. (See Example 1-12.)

Example 1-12. Vocalist bean with a PartEnum

public class Vocalist {
    private String name;
    private PartEnum part;

    public String getName( ) { return name; }
    public void setName(String name) { this.name = name; }

    public PartEnum getPart( ) { return part; }
    public void setPart(PartEnum part) { this.part = part; }
}

Checking for every voice part involves a call to PartEnum.iterator( ), which obviates the need for an if/else if control statement to catch every single voice part:

Iterator parts = PartEnum.iterator( );
while( parts.hasNext( ) ) {
  PartEnum part = (PartEnum) parts.next( );
  if( part.equals( vocalist.getPart( ) ) ) {
    System.out.println( "Vocalist is a " + part.getValue( ) );
  }
}

This example did not include any reference to a specific voice part, but, if your code needs to reference a specific voice type, it can use the static constants in PartEnumPartEnum.SOPRANO, PartEnum.ALTO, PartEnum.TENOR, and PartEnum.BASS. And, lastly, it is impossible to create a Vocalist object with an invalid voice part, as the part property must be one of the four static instances of PartEnum defined in a PartEnum or null.

Using Enum types becomes especially important if an object has multiple classifications. If you were creating an object to track a person, you could easily end up with two overlapping classifications. In Example 1-13, both jobStatus and gender can be UNKNOWN, and the valid options for both jobStatus and gender are defined as public static int variables.

Example 1-13. Using public static final constants for category information

public class Person {
    public static final int STUDENT = -1;
    public static final int EMPLOYED = 0;
    public static final int UNEMPLOYED = 1;
    public static final int RETIRED = 2;
    public static final int UNKNOWN = 3;
    public static final int MALE = 0;
    public static final int FEMALE = 1;

    private int jobStatus;
    private int gender;

    public int getJobStatus( ) { return jobStatus; }
    public void setJobStatus(int jobStatus) { this.jobStatus = jobStatus; }

    public int getGender( ) { return gender; }
    public void setGender(int gender) { this.gender = gender; }
}

This class defines two properties—jobStatus and gender—and both correspond to codes stored in the database. This system needs to know; that MALE, FEMALE, and UNKNOWN are the valid values for the gender property; and STUDENT, EMPLOYED, UNEMPLOYED, UNKNOWN, and RETIRED all correspond to the jobStatus property. This class does not define which constants are related to which property. Is UNKNOWN a gender or a jobStatus? Some systems rename these static variables to reflect which properties they correspond to—GENDER_MALE, GENDER_FEMALE, and GENDER_UNKNOWN would clearly correspond to the gender property; and JOB_STUDENT, JOB_EMPLOYED, JOB_UNEMPLOYED, JOB_UNKNOWN, and JOB_RETIRED would correspond to the jobStatus property. This solution only draws a conceptual boundary in the mind of the programmer; no logical separation is achieved, and there is still no way to guarantee that a bean property will have a valid value. (See Example 1-14.)

Example 1-14. Simplifying with ValueEnum

public class Person {
    private JobStatusEnum jobStatus;
    private GenderEnum gender;

    public JobStatusEnum getJobStatus( ) { return jobStatus; }
    public void setJobStatus(JobStatusEnum jobStatus) { 
        this.jobStatus = jobStatus; 
    }

    public GenderEnum getGender( ) { return gender; }
    public void setGender(GenderEnum gender) { this.gender = gender; }
}

The solution to this problem can be solved by extending ValuedEnum and getting rid of the confusing mish-mash of public static constants. If you find yourself frequently creating static variables to reflect categories or types, enums are the solution. And, if you haven’t upgraded to Java 1.5, use Commons Lang Enum and ValuedEnum.

See Also

As mentioned previously, Java 1.5 (or Tiger) has a new language feature: enums. If you are using Java 1.5, see the “Java 1.5 in a Nutshell” article from Sun Microsystems at http://java.sun.com/developer/technicalArticles/releases/j2se15/.

Get Jakarta Commons Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.