An enumerated type is a type whose legal values consist of a fixed set of constants.
Before enum types were added to the language, a common pattern for representing
enumerated types was to declare a group of named int
constants,
one for each member of the type:
// The int enum pattern - severely deficient!
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;
public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;
This
technique, known as the int
enum pattern, has many shortcomings. Note that the name of
each apple constant is prefixed with APPLE_
and
the name of each orange constant is prefixed with ORANGE_. This is because Java doesn’t provide namespaces
for int enum groups. Prefixes prevent
name clashes when two int enum groups have identically
named constants.
Programs that
use the int enum pattern are brittle. Because
int enums are compile-time constants,
they are compiled into the clients that use them.
There is no
easy way to translate int enum constants into printable
strings.
There is no
reliable way to iterate over all the int
enum
constants in a group, or even to obtain the size of an int enum group.
You may
encounter a variant of this pattern in which String
constants
are used in place of int constants. This variant, known as
the String
enum pattern, is even less desirable. While it does
provide printable strings for its constants, it can lead to performance
problems because it relies on string comparisons.
Luckily, as of
release 1.5, the language provides an alternative that avoids the shortcomings
of the int and string enum patterns and provides many added
benefits. It is the (JLS, 8.9). Here’s how it looks in its simplest form:
public enum Apple { FUJI, PIPPIN, GRANNY_SMITH }
public enum Orange { NAVEL, TEMPLE, BLOOD }
The basic idea
behind Java’s enum types is simple: they are classes that export one instance
for each enumeration constant via a public static final field. Enum types are
effectively final, by virtue of having no accessible constructors. Because
clients can neither create instances of an enum type nor extend it, there can
be no instances but the declared enum constants. In other words, enum types are
instance-controlled. They are a generalization of singletons (Item 3), which
are essentially single-element enums.
Enums provide
compile-time type safety.
Enum types
with identically named constants coexist peacefully because each type has its own
namespace. You can add or reorder constants in an enum type without recompiling
its clients. Finally, you can translate enums into printable strings by calling
their toString method.
In addition to
rectifying the deficiencies of int enums, enum
types let you add arbitrary methods and fields and implement arbitrary
interfaces. They provide high-quality implementations of all the Object methods, they implement Comparable (Item 12) and Serializable, and their
serialized form is designed to withstand most changes to the enum type.
For a nice
example of a rich enum type, consider the eight planets of our solar system.
Each planet has a mass and a radius, and from these two attributes you can
compute its surface gravity. This in turn lets you compute the weight of an
object on the planet’s surface, given the mass of the object. Here’s how this
enum looks. The numbers in parentheses after each enum constant are parameters
that are passed to its constructor. In this case, they are the planet’s mass
and radius:
// Enum type with data and behavior
public enum Planet {
MERCURY(3.302e+23, 2.439e6),
VENUS (4.869e+24, 6.052e6),
EARTH (5.975e+24, 6.378e6),
MARS (6.419e+23, 3.393e6),
JUPITER(1.899e+27, 7.149e7),
SATURN (5.685e+26, 6.027e7),
URANUS (8.683e+25, 2.556e7),
NEPTUNE(1.024e+26, 2.477e7);
private final double mass; // In kilograms
private final double radius; // In meters
private final double surfaceGravity; // In m / s^2
// Universal gravitational constant in m^3 / kg s^2
private static final double G = 6.67300E-11;
// Constructor
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
surfaceGravity = G * mass / (radius * radius);
}
public double mass() { return mass; }
public double radius() { return radius; }
public double surfaceGravity() { return surfaceGravity;
}
public double surfaceWeight(double mass) {
return mass * surfaceGravity; // F = ma
}
}
It is easy to
write a rich enum type such as Planet. To associate
data with enum constants, declare instance fields and write a constructor that
takes the data and stores it in the fields. Enums are by their nature
immutable, so all fields should be final (Item 15). They can be public, but it
is better to make them private and provide public accessors (Item 14).
While the Planet enum is simple, it is surprisingly powerful.
Here is a short program that takes the earth-weight of an object (in any unit)
and prints a nice table of the object’s weight on all eight planets (in the
same unit):
public class WeightTable {
public static void main(String[] args) {
double earthWeight = Double.parseDouble(args[0]);
double mass = earthWeight /
Planet.EARTH.surfaceGravity();
for (Planet p : Planet.values())
System.out.printf("Weight on %s is %f%n",
p,
p.surfaceWeight(mass));
}
}
constant-specific method implementations:
// Enum type with constant-specific method
implementations
public enum Operation {
PLUS { double apply(double x, double y){return x + y;}
},
MINUS { double apply(double x, double y){return x - y;}
},
TIMES { double apply(double x, double y){return x * y;}
},
DIVIDE { double apply(double x, double y){return x / y;}
};
abstract double apply(double x, double y);
}
Enum types
have an automatically generated valueOf(String)
method
that translates a constant’s name into the constant itself.
stringToEnum map from a static block that runs after the
constants have been created.
A disadvantage
of constant-specific method implementations is that they make it harder to
share code among enum constants.
For example,
consider an enum representing the days of the week in a payroll package. This
enum has a method that calculates a worker’s pay for that day given the
worker’s base salary
(per hour) and
the number of hours worked on that day. On the five weekdays, any time worked
in excess of a normal shift generates overtime pay; on the two weekend days,
all work generates overtime pay.
// Enum that switches on its value to share code –
questionable
enum PayrollDay {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,
SATURDAY, SUNDAY;
private static final int HOURS_PER_SHIFT = 8;
double pay(double hoursWorked, double payRate) {
double basePay = hoursWorked * payRate;
double overtimePay; //
Calculate overtime pay
switch(this) {
case SATURDAY: case SUNDAY:
overtimePay = hoursWorked * payRate / 2;
default: // Weekdays
overtimePay = hoursWorked <= HOURS_PER_SHIFT ?
0 : (hoursWorked - HOURS_PER_SHIFT) * payRate / 2;
break;
}
return basePay + overtimePay;
}
}
This code is
undeniably concise, but it is dangerous from a maintenance perspective.
Luckily, there
is a nice way to achieve this. The idea is to move the overtime pay computation
into a private nested enum, and to pass an instance of this strategy enum to the constructor for the PayrollDay
enum.
The PayrollDay enum then
delegates the overtime pay calculation to the strategy enum, eliminating the
need for a switch statement or constant-specific method implementation in PayrollDay. While this pattern is less concise than the
switch statement, it is safer and more flexible:
// The strategy enum pattern
enum PayrollDay {
MONDAY(PayType.WEEKDAY), TUESDAY(PayType.WEEKDAY),
WEDNESDAY(PayType.WEEKDAY), THURSDAY(PayType.WEEKDAY),
FRIDAY(PayType.WEEKDAY),
SATURDAY(PayType.WEEKEND), SUNDAY(PayType.WEEKEND);
private final PayType payType;
PayrollDay(PayType payType) { this.payType = payType; }
double pay(double hoursWorked, double payRate) {
return payType.pay(hoursWorked, payRate);
}
// The strategy enum type
private enum PayType {
WEEKDAY {
double overtimePay(double hours, double payRate) {
return hours <= HOURS_PER_SHIFT ? 0 :
(hours - HOURS_PER_SHIFT) * payRate / 2;
}
},
WEEKEND {
double overtimePay(double hours, double payRate) {
return hours * payRate / 2;
}
};
private static final int HOURS_PER_SHIFT = 8;
abstract double overtimePay(double hrs, double payRate);
double pay(double hoursWorked, double payRate) {
double basePay = hoursWorked * payRate;
return basePay + overtimePay(hoursWorked, payRate);
}
}
}
Switches on
enums are good for augmenting external enum types with constant-specific
behavior.
Enums are,
generally speaking, comparable in performance to int
constants.
A minor performance disadvantage of enums over int
constants
is that there is a space and time cost to load and initialize enum types.
Except on resource-constrained devices, such as cell phones and toasters, this
is unlikely to be noticeable in practice.
In summary,
the advantages of enum types over int
constants
are compelling. Enums are far more readable, safer, and more powerful. Many
enums require no explicit constructors or members, but many others benefit from
associating data with each constant and providing methods whose behavior is
affected by this data. Far fewer enums benefit from associating multiple
behaviors with a single method. In this relatively rare case, prefer
constant-specific methods to enums that switch on their own values. Consider
the strategy enum pattern if multiple enum constants share common behaviors.
Reference: Effective Java 2nd Edition by Joshua Bloch