The Enum is perfect... well almost.
Ok so its time to admit to something. I'm deeply in love with Java's Enum! In my opinion it was the best part of Java 5 and I still wonder why it took Sun 10 years to add this powerful static modeling construct.
The power of the Enum
Although there certainly are known limitations of the Enum, i.e. how you can't extend it, I have never run into a practical limitation myself. Until now that is. The issue I want to raise is that of Enum forward referencing.
The great Alan Turing taught us that at the end of the day, everything can be modeled by a Turing Machine and finite automata. We may not often consciously operate at this level, but many things still makes sense to model this way being it a regular expression matcher, navigation rules or similar. The Java Enum appears to be a perfectly simple, fast and type-safe way of modeling this... or does it?
State machine with an Enum
Since the Java Enum effectively is just a group of static class instances, it allows us to attach methods and state to each static instance. We should be able to use this fact to express a transitive relationship with outself. To model a CD player, we'd write something like this:
Except that won't compile! The compiler complains with an "illegal forward reference" error. I can understand why it's the easiest to simply not allow this circular use case, but considering everything else in Java allows forward referencing (i.e. we don't need header files as in C++) it would've been nice if that was the case with Enum's too. The JLS simply states:
The power of the Enum
Although there certainly are known limitations of the Enum, i.e. how you can't extend it, I have never run into a practical limitation myself. Until now that is. The issue I want to raise is that of Enum forward referencing.
The great Alan Turing taught us that at the end of the day, everything can be modeled by a Turing Machine and finite automata. We may not often consciously operate at this level, but many things still makes sense to model this way being it a regular expression matcher, navigation rules or similar. The Java Enum appears to be a perfectly simple, fast and type-safe way of modeling this... or does it?
State machine with an Enum
Since the Java Enum effectively is just a group of static class instances, it allows us to attach methods and state to each static instance. We should be able to use this fact to express a transitive relationship with outself. To model a CD player, we'd write something like this:
public enum PlayerState {
STOPPED(PlayerState.PLAYING),
PLAYING(PlayerState.STOPPED, PlayerState.PAUSED),
PAUSED(PlayerState.PLAYING, PlayerState.STOPPED);
private final EnumSet<PlayerState> transitions;
PlayerState(final PlayerState... transitions){
this.transitions = EnumSet.copyOf( Arrays.asList(transitions));
}
// State transition and query logic below...
}
Except that won't compile! The compiler complains with an "illegal forward reference" error. I can understand why it's the easiest to simply not allow this circular use case, but considering everything else in Java allows forward referencing (i.e. we don't need header files as in C++) it would've been nice if that was the case with Enum's too. The JLS simply states:
"It is a compile-time error for the constructors, instance initializer blocks, or instance variable initializer expressions of an enum constant e to refer to itself or to an enum constant of the same type that is declared to the right of e"Granted, it has been some years since my last compiler course but I can't see any reason not to allow forward referencing in this particular case where everything is known. I suspect the culprit simply being that Enum, as with so many other things, was added later using only existing features such as not to change existing initialization rules of the JVM.
Comments
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;
}
(... some initialization cut out....)
}
}
Not 100% sure how it helps my case though, it's not at all clear from his presentation how client code of this workaround looks like and it seems to focus an awfully lot on explicit transitions rather than having these be modeled implicit by a state relationship.
You are required to uniquely name each transition. In Bloch's example he has the advantage of having naturally unique transition names even if they go to the same state (i.e. going to a liquid is either by melting or condensing). However many thing you may wish to model does not lend itself to such rich prose. In my example, how would you name the two transitions going to stop? I suppose you could call them STOP_FROM_PLAYING and STOP_FROM_PAUSED but I can't imagine this naming scheme scale and read very well in real application scenarios.
Interesting. I suppose the Eclipse compiler always were a bit more lenient. It does not work on the Sun based compilers however.
The reason is constant Enum types are initialized in the static block [of the particular Enum type by the compiler-generated code]. In other word, they call upon either a user-defined constructor or a synthetically created one by the compiler. Therefore, the compiler cannot certainly determine whether the initialization has been thoroughly performed until the static block is completed. At the same time, as I said, the instantiations are performed in the same static block. It is obvious that the compiler cannot use an Enum constant type when it hasn't been initialized--hence the compile-time error.
P.S. Compiler allows a constant field that its value is known at compile-time, for instance a constant, in the constructors or field variables. i.e.
enum EnumT {
DOO, DAA;
static z = 7;
static final y = 9;
EnumT() {
int i = y; // OK
int k = z; // Compile-time error
}
}
I dived a little deeper into the issue btw. in a later entry: http://coffeecokeandcode.blogspot.com/2008/12/java-enum-relational-modelling.html
I was searching for a workaround for the exact same issue.
eventually I resolved it with
public enum PlayerState {
STOPPED(),
PLAYING(),
PAUSED();
static {
STOPPED.setTransitions(PlayerState.PLAYING);
PLAYING.setTransitions(PlayerState.STOPPED, PlayerState.PAUSED);
PAUSED.setTransitions(PlayerState.PLAYING, PlayerState.STOPPED);
}
private final EnumSet transitions;
PlayerState(){}
private void setTransitions(final PlayerState... transitions){
this.transitions = EnumSet.copyOf( Arrays.asList(transitions));
}
// State transition and query logic below...
}
public enum PlayerState {
STOPPED {
@Override
protected Collection getTransitions() {
return EnumSet.of(PlayerState.PLAYING);
}
},
PLAYING {
@Override
protected Collection getTransitions() {
return EnumSet.of(PlayerState.STOPPED, PlayerState.PAUSED);
}
},
PAUSED {
@Override
protected Collection getTransitions() {
return EnumSet.of(PlayerState.PLAYING, PlayerState.STOPPED);
}
};
protected abstract Collection getTransitions();
}