Posted by Sander van Der Waal at 1:16 on Tuesday 13 May
As Erik-Berndt posted earlier, the second edition of Joshua Bloch’s bestseller ‘Effective Java’ is out. The author himself gave a session on the new features in this book on Friday morning. If you already have the book, read items 28 on Generics, 31-34 and 77 on Enum types, and 71 on Lazy initialization. For this blog, I will focus on three specific Enum examples he showed that I think can be useful.
The first one is easiest. Suppose you want to write an enum that contains the number of musicians in different formations.
This is what it may look like:
public enum Ensemble {
SOLO, DUET, TRIO, QUARTET, QUINTET,
SEXTET, SEPTET, OCTET, NONET, DECTET;
public int numberOfMusicians()
{
// ???
}
}
Now how do you determine the number of musicians in these ensembles?
At first glance, you could suggest using return ordinal() + 1; to determine the number of musicians.
The problem with this approach is that it is a maintenance nightmare. You cannot add multiple constants with the same integer value, and you get in trouble when you wnat to insert a new type of ensemble that does not have the number of the previous type + 1. Here a better way to do it that solves all the problems mentioned and looks quite elegant:
public enum Ensemble {
SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5),
SEXTET(6), SEPTET(7), OCTET(8), DOUBLE_QUARTET(8),
NONET(9), DECTET(10), TRIPLE_QUARTET(12);
private final int numberOfMusicians;
Ensemble(int size) {
numberOfMusicians = size;
}
public int numberOfMusicians() {
return numberOfMusicians;
}
}
Secondly, enums are a good replacement for the old approach where you used the bits of an integer to represent up to 32 boolean variables, like in this example:
public class Text {
public static final int STYLE_BOLD = 1;
public static final int STYLE_ITALIC = 2;
public static final int STYLE_UNDERLINE = 4;
public static final int STYLE_STRIKETHROUGH = 8;
// Param is bitwise OR of 0 or more STYLE_ values
public void applyStyles(int styles) {
// ...
}
}
This type of usage can cause some problems. For example, bit fields are not safe, there are are no namespaces (forcing you to prefix the constant names) and there is no easy way to iterate over the elements. With enums, you get an elegant alternative:
public class Text {
public enum Style {
BOLD, ITALIC, UNDERLINE, STRIKETHROUGH
}
// Any Set could be passed in, but EnumSet is best
public void applyStyles(Set<Style> styles) {
//...
}
}
As a client using this code, this is an example of how to call this method:text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));
This way, you solve all the problems mentioned. But the best thing is the compiler will optimize your code to the former layout with bit fields, so it won’t ‘cost’ you anything!
Finally, here is an example of how you can model the transitions of one of the three physical phases into another. This code is common, but flawed, according to Bloch:
public enum Phase {
SOLID, LIQUID, GAS;
public enum Transition {
MELT, FREEZE, BOIL, CONDENSE, SUBLIME, DEPOSIT;
// Rows indexed by src-ordinal, cols by dst-ordinal
private static final Transition[][] TRANSITIONS = {
{ null, MELT, SUBLIME },
{ FREEZE, null, BOIL },
{ DEPOSIT, CONDENSE, null }
};
// Returns phase transition from one phase to another
public static Transition from(Phase src, Phase dst) {
return TRANSITIONS[src.ordinal()][dst.ordinal()];
}
}
The problems with this implementation are not too hard to see. It is error-prone: mistakes in the transition table cause runtime failures. Furthermore, you easily mess up the table when adding behavior. Also readibility may become a problem if you have many phases.
According to Joshua Bloch, an EnumMap is the way to go. And I must agree, this code definitely looks a lot better:
public enum Phase {
SOLID, LIQUID, GAS;
public enum Transition {
MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID),
BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID),
SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID);
private final Phase src;
private final Phase dst;
Transition(Phase src, Phase dst) {
this.src = src;
this.dst = dst;
}
}
However, the class gets somewhat more complicated when we take a look at the from-method adding behavior to return the correct transition:
// Initialize the phase transition map
private static final Map<Phase, Map<Phase,Transition>> m =
new EnumMap<Phase, Map<Phase,Transition>>(Phase.class);
static {
// Insert empty map for each src state
for (Phase p : Phase.values())
m.put(p,new EnumMap<Phase,Transition>(Phase.class));
// Insert state transitions
for (Transition trans : Transition.values())
m.get(trans.src).put(trans.dst, trans);
}
public static Transition from(Phase src, Phase dst) {
return m.get(src).get(dst);
}
}
But the best thing about this second part is that it is just some administration code that you add once, is initialized once when the class is loaded, and does not need maintenance in case you add data. For instance, if you add support for the PLASMA state, all you have to do is adding the PLASMA constant to Phase, and IONIZE(GAS, PLASMA) plus DEIONIZE(PLASMA, GAS) to Transition, and that's it! The rest can remain untouched.
Much more of these kinds of helpful insights are added in the second edition of Effective Java!














