What are enums and why are they useful?
You should always use enums when a variable (especially a method parameter) can only take one out of a small set of possible values. Examples would be things like type constants (contract status: "permanent", "temp", "apprentice"), or flags ("execute now", "defer execution").
If you use enums instead of integers (or String codes), you increase compile-time checking and avoid errors from passing in invalid constants, and you document which values are legal to use.
BTW, overuse of enums might mean that your methods do too much (it's often better to have several separate methods, rather than one method that takes several flags which modify what it does), but if you have to use flags or type codes, enums are the way to go.
As an example, which is better?
/** Counts number of foobangs.
* @param type Type of foobangs to count. Can be 1=green foobangs,
* 2=wrinkled foobangs, 3=sweet foobangs, 0=all types.
* @return number of foobangs of type
*/
public int countFoobangs(int type)
versus
/** Types of foobangs. */
public enum FB_TYPE {
GREEN, WRINKLED, SWEET,
/** special type for all types combined */
ALL;
}
/** Counts number of foobangs.
* @param type Type of foobangs to count
* @return number of foobangs of type
*/
public int countFoobangs(FB_TYPE type)
A method call like:
int sweetFoobangCount = countFoobangs(3);
then becomes:
int sweetFoobangCount = countFoobangs(FB_TYPE.SWEET);
In the second example, it's immediately clear which types are allowed, docs and implementation cannot go out of sync, and the compiler can enforce this. Also, an invalid call like
int sweetFoobangCount = countFoobangs(99);
is no longer possible.
Why use any programming language feature? The reason we have languages at all is for
- Programmers to efficiently and correctly express algorithms in a form computers can use.
- Maintainers to understand algorithms others have written and correctly make changes.
Enums improve both likelihood of correctness and readability without writing a lot of boilerplate. If you are willing to write boilerplate, then you can "simulate" enums:
public class Color {
private Color() {} // Prevent others from making colors.
public static final Color RED = new Color();
public static final Color AMBER = new Color();
public static final Color GREEN = new Color();
}
Now you can write:
Color trafficLightColor = Color.RED;
The boilerplate above has much the same effect as
public enum Color { RED, AMBER, GREEN };
Both provide the same level of checking help from the compiler. Boilerplate is just more typing. But saving a lot of typing makes the programmer more efficient (see 1), so it's a worthwhile feature.
It's worthwhile for at least one more reason, too:
Switch statements
One thing that the static final
enum simulation above does not give you is nice switch
cases. For enum types, the Java switch uses the type of its variable to infer the scope of enum cases, so for the enum Color
above you merely need to say:
Color color = ... ;
switch (color) {
case RED:
...
break;
}
Note it's not Color.RED
in the cases. If you don't use enum, the only way to use named quantities with switch
is something like:
public Class Color {
public static final int RED = 0;
public static final int AMBER = 1;
public static final int GREEN = 2;
}
But now a variable to hold a color must have type int
. The nice compiler checking of the enum and the static final
simulation is gone. Not happy.
A compromise is to use a scalar-valued member in the simulation:
public class Color {
public static final int RED_TAG = 1;
public static final int AMBER_TAG = 2;
public static final int GREEN_TAG = 3;
public final int tag;
private Color(int tag) { this.tag = tag; }
public static final Color RED = new Color(RED_TAG);
public static final Color AMBER = new Color(AMBER_TAG);
public static final Color GREEN = new Color(GREEN_TAG);
}
Now:
Color color = ... ;
switch (color.tag) {
case Color.RED_TAG:
...
break;
}
But note, even more boilerplate!
Using an enum as a singleton
From the boilerplate above you can see why an enum provides a way to implement a singleton. Instead of writing:
public class SingletonClass {
public static final void INSTANCE = new SingletonClass();
private SingletonClass() {}
// all the methods and instance data for the class here
}
and then accessing it with
SingletonClass.INSTANCE
we can just say
public enum SingletonClass {
INSTANCE;
// all the methods and instance data for the class here
}
which gives us the same thing. We can get away with this because Java enums are implemented as full classes with only a little syntactic sugar sprinkled over the top. This is again less boilerplate, but it's non-obvious unless the idiom is familiar to you. I also dislike the fact that you get the various enum functions even though they don't make much sense for the singleton: ord
and values
, etc. (There's actually a trickier simulation where Color extends Integer
that will work with switch, but it's so tricky that it even more clearly shows why enum
is a better idea.)
Thread safety
Thread safety is a potential problem only when singletons are created lazily with no locking.
public class SingletonClass {
private static SingletonClass INSTANCE;
private SingletonClass() {}
public SingletonClass getInstance() {
if (INSTANCE == null) INSTANCE = new SingletonClass();
return INSTANCE;
}
// all the methods and instance data for the class here
}
If many threads call getInstance
simultaneously while INSTANCE
is still null, any number of instances can be created. This is bad. The only solution is to add synchronized
access to protect the variable INSTANCE
.
However, the static final
code above does not have this problem. It creates the instance eagerly at class load time. Class loading is synchronized.
The enum
singleton is effectively lazy because it's not initialized until first use. Java initialization is also synchronized, so multiple threads can't initialize more than one instance of INSTANCE
. You're getting a lazily initialized singleton with very little code. The only negative is the the rather obscure syntax. You need to know the idiom or thoroughly understand how class loading and initialization work to know what's happening.