Enums in Java (One More Time)
Pages: 1, 2, 3
A Mini-Language for Enums
I have designed a mini-language called jEnum for declaring enums, and I
have written a small "compiler-like" translator that translates
jEnum declarations into the equivalent Java class. For example, the
declaration for enum Day given in the beginning of this article is
translated into the equivalent Java class listed above--this is actually
how the class was created. Moreover, jEnum permits an optional package
declaration (simply copied over into the class) and an optional class
comment (also copied), and allows the specification of underlying integer
values and string representations for the objects, as illustrated in the
following example:
package com.softmoore.util;
/**
* Various USA coins
*/
enum Coin { PENNY("penny") = 1, NICKEL("nickel") = 5, DIME("dime") = 10,
QUARTER("quarter") = 25, HALF_DOLLAR("half dollar") = 50 };
Although the enums-as-integer-constants alternative has merit in certain circumstances, the enums-as-objects alternative is generally preferable because of its type safety. Of course the enums-as-objects alternative still has its shortcomings. This section briefly outlines jEnum, which retains the strengths of the enums-as-objects alternative while at the same time providing a compact, efficient declaration of enumerated values. In other words, it also satisfies criteria number 2.
For the compiler-oriented reader, here is the simple grammar for the jEnum mini-language.
compilationUnit = ( packageDecl )? ( docComment )? enumTypeDecl .
packageDecl = "package" packagePath ";" .
packagePath = packageName ( "." packageName )* .
docComment = "/**" commentChars "*/" .
enumTypeDecl = "enum" enumTypeName "{" enumList "}" ";" .
enumList = enumDecl ( "," enumDecl )* .
enumDecl = enumLiteral ( "(" stringLiteral ")" )? ( "=" intLiteral )? .
packageName = identifier .
enumTypeName = identifier .
enumLiteral = identifier .
commentChars = any-char-sequence-except-"*/"
The grammar allows an optional package declaration in the beginning that
looks just like any Java package declaration, optionally followed by a standard
Javadoc comment. The enum type declaration begins with the word
"enum" (itself also a reserved word in the jEnum
language). The enumerated values are enclosed in curly braces and are
separated by commas. Each declaration of an enumerated value consists of a
standard Java identifier, optionally followed by a string literal enclosed in
parentheses, optionally followed by the assignment operator "=" and
an integer literal.
If you omit the string literal, then the name for the enumerated value
will be used. If you omit the assignment of an integer literal, then the
compiler will assign values sequentially after the last assigned value,
starting with zero, if none are assigned. The string values are returned as
part of the toString() method, and the integer values are returned
by the ord() method. For example, given the following enum
declaration:
enum Color { RED("Red") = 2, WHITE("White") = 4, BLUE };
REDhas the label "Red" and ord 2WHITEhas the label "White" and ord 4BLUEhas the label "BLUE" and ord 5
Note that Java reserved words are still reserved in jEnum; you can't
have a package named "this" or an enum type named
"for." Additionally, all string labels (string
literals or enumerated value names) must be distinct, and all integer literals
must be strictly increasing. Thus the following would not compile, as labels
are not distinct:
enum Color { RED("Red"), WHITE("BLUE"), BLUE };
Nor would this declaration compile, because integers are not increasing
(since WHITE is auto-assigned the value 2):
enum Color { RED = 1, WHITE, BLUE = 2 };
As a more complete and realistic example, consider this enum type declaration that, through the magic of bootstrapping, is actually used by the jEnum translator.
package com.softmoore.jEnum;
/**
* This class encapsulates the symbols (a.k.a. token types)
* of a language token.
*/
enum Symbol {
identifier,
enumRW("Reserved Word: enum"),
abstractRW("Reserved Word: abstract"),
assertRW("Reserved Word: assert"),
booleanRW("Reserved Word: boolean"),
breakRW("Reserved Word: break"),
byteRW("Reserved Word: byte"),
caseRW("Reserved Word: case"),
catchRW("Reserved Word: catch"),
charRW("Reserved Word: char"),
classRW("Reserved Word: class"),
constRW("Reserved Word: const"),
continueRW("Reserved Word: continue"),
defaultRW("Reserved Word: default"),
doRW("Reserved Word: do"),
doubleRW("Reserved Word: double"),
elseRW("Reserved Word: else"),
extendsRW("Reserved Word: extends"),
finalRW("Reserved Word: final"),
finallyRW("Reserved Word: finally"),
floatRW("Reserved Word: float"),
forRW("Reserved Word: for"),
gotoRW("Reserved Word: goto"),
ifRW("Reserved Word: if"),
implementsRW("Reserved Word: implements"),
importRW("Reserved Word: import"),
instanceOfRW("Reserved Word: instanceOf"),
intRW("Reserved Word: int"),
interfaceRW("Reserved Word: interface"),
longRW("Reserved Word: long"),
nativeRW("Reserved Word: native"),
newRW("Reserved Word: new"),
nullRW("Reserved Word: null"),
packageRW("Reserved Word: package"),
privateRW("Reserved Word: private"),
protectedRW("Reserved Word: protected"),
publicRW("Reserved Word: public"),
returnRW("Reserved Word: return"),
shortRW("Reserved Word: short"),
staticRW("Reserved Word: static"),
strictfpRW("Reserved Word: strictfp"),
superRW("Reserved Word: super"),
switchRW("Reserved Word: switch"),
synchronizedRW("Reserved Word: synchronized"),
thisRW("Reserved Word: this"),
throwRW("Reserved Word: throw"),
throwsRW("Reserved Word: throws"),
transientRW("Reserved Word: transient"),
tryRW("Reserved Word: try"),
voidRW("Reserved Word: void"),
volatileRW("Reserved Word: volatile"),
whileRW("Reserved Word: while"),
equals("="),
leftParen("("),
rightParen(")"),
leftBrace("{"),
rightBrace("}"),
comma(","),
semicolon(";"),
period("."),
intLiteral,
stringLiteral,
docComment,
EOF,
unknown
};
The mini-language jEnum translator accompanies this article. If the enum
declaration for Day is defined in a file named
"Day.enum," then the command
$ java -jar jEnum.jar Day.enum
will generate the complete class Day.java shown above for the
enums-as-objects alternative, including standard Javadoc comments for the
methods. You can combine the call to java and its command line
arguments into an executable file (a Unix shell script or a Windows batch
file), so that the translator can be invoked as simply as:
$ jec Day.enum
There are four important points that you should know about using the jEnum
translator. First, the source file name does not have to end in
".enum." Any suffix is accepted as long as the full
file name is supplied.
Second, if the file name does end in the suffix
.enum, then supplying the suffix as part of the
command line is optional. The translator will search first for a file with the
exact name specified on the command line, and then for a file ending in
.enum. The following is also acceptable:
$ java -jar jEnum.jar Day
|
Related Reading
Mac OS X for Java Geeks |
Third, the name of the Java file produced by the translator is based on the
name given in the enum definition, not the name of the source file. In other
words, if the file were named simply "d.enum," the
Java source file would still be named "Day.java." Of
course, good naming conventions recommend naming the source file
"Day.enum."
Fourth, the compiler accepts three flags that allow you to generate the different enum alternatives as follows:
-ogenerates the enums-as-objects alternative. (This is the default.)-cgenerates the enums-as-integer-constants alternative, implemented as a class.-igenerates the enums-as-integer-constants alternative, implemented as an interface.
Additionally, the -c flag not only creates the
class containing the integer constants, but also attempts to provide some of the
functionality of the enums-as-objects approach by defining several public
static methods such as first(), last(),
toString(int n), prev(int n), and next(int
n).
Resources
Complete code for the jEnum compiler, plus several examples and Javadoc files for this article, are available in jEnum.zip
Adding type-safe enums is one of the most requested enhancements for Java. See developer.java.sun.com/developer/bugParade/bugs/4401321.html.
Eric Armstrong wrote one of the earliest (and still one of the best) articles describing enums in Java in "Create enumerated constants in Java."
For another excellent reference on typesafe enums in Java, see Chapter 5: "Substitutes for Missing C Constructs" of Joshua Bloch's Effective Java Programming Language Guide (Addison-Wesley, 2001; ISBN: 0201310058); specifically, "Item 21: Replace enum constructs with classes."
Glen McCluskey provides yet another clear explanation of the typesafe enum construct in this Java Developer Connection (JDC) Tech Tip, "Using Enumerations in Java Programming," (August 2001).
James Gosling and Henry McGilton originally advised Java programmers on how to cope with Java's enum absence in "The Java Language Environment" whitepaper (see the "2.2.3 No Enums" section).
"Using Enumerations in Java Programming," Java Developer Connection (JDC) Tech Tips, August 7, 2001.
"Using readResolve," Java Developer Connection (JDC) Tech Tips, February 5, 2002.
"Java Q&A: Constants, I do declare, Readers describe how they employ constants," by Tony Sintes.
"Use constant types for safer and cleaner code," by Thomas E. Davis.
John I. Moore, Jr. is Principal Consultant for SoftMoore Consulting.
Return to ONJava.com.