What:
A personal exploration on why Enum
is declared as Enum<E extends Enum<E>>
Why
I stumbled upon this interesting question a while ago when a co-worker showed it to me. Googling it provied enough links to satisfy my initial curiosity, but recently the question surfaced again; so I tried to implement my own version of the explanation provided online to "see for myself".
What follows is largely based on Angelika Langer's FAQ and can be thought of as my worked-out version of that page. My enum class is called Enum1
to avoid clash with the SDK's version; and has been tested with JDK 1.7.0_07.
The exploration
Say you're the Java team and you want to support syntax like:
enum Color(RED, GREEN,BLUE);
... so that it could be used like so:
- As constants :
Color.RED
... which means that public static constants must be available - In variables :
Color color=Color.BLUE
... which means that it must of type Color - In comparison :
color==Color.BLUE
... which means it must implementcompareTo()
Further, say you intend to add this feature with as little syntactic sugar as possible; so you want to convert the basic enum
syntax alone via a special operation in the compiler and generate everything else using standard Java language constructs.
The generated code would look something like so:
public class Color implements Comparable<E>{
private static final Color RED;
private static final Color BLUE;
private static final Color GREEN;
private static final Color $VALUES[];
static{
RED = new Color("RED",0);
BLUE = new Color("BLUE",1);
GREEN = new Color("GREEN",2);
$VALUES = (new Color[]{ RED, BLUE, GREEN });
}
private final String name;
public final int ordinal;
//private since nobody outside needs to use this
private Color(String name, int ordinal){
this.name = name;
this.ordinal = ordinal;
}
public final int compareTo(Color c){
return this.ordinal-c.ordinal;
}
}
If you wrote more than one of these, you'd have quite a bit of logic that's common and could be extracted out:
- Each item in the enumeration has a name and an ordinal.
- Each item is built privately and the instances built statically are made available publicly as constants and via an array
- The constants have comparable values (optionally internal)
So you could write a base class like so:
public class Enum1<E> implements Comparable<E>{ //line1
protected static Enum1 $VALUES[]={};
private final String name;
public final int ordinal;
protected Enum1(String name, int ordinal){
this.name = name;
this.ordinal = ordinal;
}
public final int compareTo(E e){
return this.ordinal-e.ordinal; //line 2
}
}
... where Enum1 needs to be defined with a generic type E because any implementation of Comparable needs to be generic. Doing that, however will cause the compiler to throw a warning that line 2 above may be using unsafe operations. This is because Enum1<E>
is equivalent to Enum1<E extends Object>
; so we could potentially send in any subclass of Object
into compareTo()
.
To fix that, we'll need to constrain the type of E
to Enum1
s or its subclasses. This can be done by changing the type parameter in line 1 to : Enum1<E extends Enum1>
. The modified code thus becomes:
public class Enum1<E extends Enum1> implements Comparable<E>{ //line1
protected static Enum1 $VALUES[]={};
private final String name;
public final int ordinal;
protected Enum1(String name, int ordinal){
this.name = name;
this.ordinal = ordinal;
}
public final int compareTo(E e){
return this.ordinal-e.ordinal; //line 2
}
}
Now Color
can be written as:
public class Color extends Enum1<Color>{
public static final Color RED;
public static final Color BLUE;
public static final Color GREEN;
static{
RED = new Color("RED",0);
BLUE = new Color("BLUE",1);
GREEN = new Color("GREEN",2);
$VALUES = (new Color[]{ RED, BLUE, GREEN });
}
protected Color(String name, int ordinal)
{ super(name,ordinal); }
}
...and test code can be written like so:
public class TestEnum1{
public static void main(String[] args){
Color c1=Color.RED;
Color c2=Color.RED;
Color c3=Color.BLUE;
Color c4=Color.GREEN;
System.out.println((c1==c2)); // 0
System.out.println(c1.compareTo(c3)); //-1
System.out.println(c4.compareTo(c3)); // 1
}
}
We can even write another enum like so:
public class Vehicle extends Enum1<Vehicle>{
public static final Vehicle BIKE;
public static final Vehicle CAR;
public static final Vehicle BUS;
static{
BIKE = new Vehicle("BIKE",0);
CAR = new Vehicle("CAR",1);
BUS = new Vehicle("BUS",2);
$VALUES = (new Vehicle[]{ BIKE, CAR, BUS });
}
protected Vehicle(String name, int ordinal)
{ super(name,ordinal); }
}
... and enhance the test code to check out things like the ordinals of two Enum1
-derived enumerations not matching even though the numerical values are same:
public class TestEnum1{
public static void main(String[] args){
Color c1=Color.RED;
// ...
Vehicle v1=Vehicle.BIKE;
// fails with error: incomparable types
System.out.println((c1==v1));
}
}
Pending Question
So I've still not figured out what the final <E>
in:
public class Enum1<E extends Enum1<E>>
... is for, considering the code above works without it.
No comments:
Post a Comment