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]
}