Thursday, May 24, 2007

Why java enum cannot extends?


UPDATE: There is an RFE (Request For Enhancement) in the Java Bug DB that you can vote for: RFE-6570766.
Please make this part of JDK 8!


The original need
I came around this issue in the end of 2004 when trying to parse Rinex files (GPS satellites data). Using JDK 5 I used enum to defined the file format. It was very clean, readable, and made the reading and writing of the parser quite easy. This system of using enum for static grammar declaration is actually used for the javac parser itself also.
The issue is that I needed to declared multiple enum for each type of lines and all enum entry contained the exact same data. So I ended up with something like:
public enum HeaderFormat {
info( "RINEX VERSION / TYPE","F9.2,11X,A1,19X" ,VERSION,TYPE),
creator( "PGM / RUN BY / DATE ","A20,A20,A20" ,PROGRAM,RUN_BY,DATE),
comment( "COMMENT ","A60" ,COMMENT),
endOfHeader("END OF HEADER ","60X" ,"");

private final RinexLineFormat _lineFormat;

HeaderFormat(String label, String format, String... fields) {
this._lineFormat = new RinexLineFormatImpl(label,format,fields);
}

public RinexLineFormat getLineFormat() {
return _lineFormat;
}
}
I needed to delegate all my enum static data to an external class, an I needed to delegate (copy/paste) this for all my XXXFormat enum.
I had one that look like:
public enum NavigationBodyFormat {
info(
"I2,1X,I2.2,1X,I2,1X,I2,1X,I2,1X,I2,F5.1,3D19.12",
SATELLITE_NUMBER,YEAR,MONTH,DAY,HOUR,MINUTE,SECOND,SV_CLOCK_BIAS,SV_CLOCK_DRIFT,SV_CLOCK_DRIFT_RATE),
brodacastOrbit1(
"3X,4D19.12",
IODE,CRS,DELTA_N,M0),
brodacastOrbit2(
"3X,4D19.12",
CUC,ECCENTRICITY,CUS,SQRT_A),
brodacastOrbit3(
"3X,4D19.12",
TOE,CIC,OMEGA_BIG,CIS);

private final RinexLineFormat _lineFormat;

NavigationBodyFormat(RinexLineFormat lineFormat) {
_lineFormat = lineFormat;
}

NavigationBodyFormat(
String format, String... fields) {
this._lineFormat = new RinexLineFormatImpl(null,format,fields);
}

public RinexLineFormat getLineFormat() {
return _lineFormat;
}

public void read(String line, Map<String, Object> container) {
getLineFormat().read(
line,container);
}
}
I was not too happy about this but it was OK.

But, then we started to do a lot of JPA migrations and the need for property declaration and/or Method and Field references, and their usage in annotations was raised again.

When trying to solve this problem, I wrote jfrog reflect xref and found out that I needed to extends enum again.

Solution
I try to read all around this extends limitation Self types (aka type of this) from Peter Ahe, and for me it resumes to one point: ''You cannot extends a class that is self-referencing in its generics.'' I may be totally wrong here, but anyway I'll stick to that.

So, that's why java.lang.Enum cannot be extended. But if you allow only one extension that will replace the class Enum, it should be quite easy. So I will really like to see "abstract enum" appears in JDK 7. The Rinex parser code will then look like:
public abstract enum EnumFormat {
private final String _label;
private final String _format;
private final String[] _fields;
private final RinexLineReader _lineReader;

EnumFormat(String label, String format, String fields) {
this._label = label;
this._format = format;
StringTokenizer fieldsStringTokenizer = new StringTokenizer(fields, ",");
List fieldsList = new ArrayList();
while (fieldsStringTokenizer.hasMoreTokens()) {
fieldsList.add(fieldsStringTokenizer.nextToken());
}
this._fields = fieldsList.toArray(new String[fieldsList.size()]);
_lineReader = new RinexLineReaderImpl(this);
}

EnumFormat(
String label, String format, String... fields) {
this._label = label;
this._format = format;
this._fields = fields;
_lineReader = new RinexLineReaderImpl(this);
}

public String getLabel() {return _label;}
public String getFormat() {return _format;}
public String[] getFields() {return _fields;}
public RinexLineReader getLineReader() {return _lineReader;}

public void read(String line, Map<String, Object> container) {
getLineReader().read(
line,container);
}
}
And my format enum declaration very straight forward, without any java code just grammar declaration like:
public enum HeaderFormat extends EnumFormat {
info( "RINEX VERSION / TYPE","F9.2,11X,A1,19X" ,VERSION,TYPE),
creator( "PGM / RUN BY / DATE ","A20,A20,A20" ,PROGRAM,RUN_BY,DATE),
comment( "COMMENT ","A60" ,COMMENT),
endOfHeader("END OF HEADER ","60X" ,"");
}
and
public enum NavigationBodyFormat extends EnumFormat {
info(
"I2,1X,I2.2,1X,I2,1X,I2,1X,I2,1X,I2,F5.1,3D19.12",
SATELLITE_NUMBER,YEAR,MONTH,DAY,HOUR,MINUTE,SECOND,SV_CLOCK_BIAS,SV_CLOCK_DRIFT,SV_CLOCK_DRIFT_RATE),
brodacastOrbit1(
"3X,4D19.12",
IODE,CRS,DELTA_N,M0),
brodacastOrbit2(
"3X,4D19.12",
CUC,ECCENTRICITY,CUS,SQRT_A),
brodacastOrbit3(
"3X,4D19.12",
TOE,CIC,OMEGA_BIG,CIS);
}

Using it for property
So, how "abstract enum" can solve some issues for Java 7 like:

First, I have to say that I really don't want to see "->" comes to Java. I used to be a C++ developer and removing from by brain the significance of "->" was a huge relief. So, please no more '''->'''

To target the above issues, I'm using Typesafe Reflections, which will provide me with 3 kind of "abstract enum": FieldDefinition, MethodDefinition, and PropertyDefinition.

The abstract enum java.lang.reflect.PropertyDefinition will look like:
public abstract enum PropertyDefinition {
private final Class modelClass;
private final String fieldName;
private final String getterMethodName;
private final String setterMethodName;
private Field reflectField;
private Method getterMethod;
private Method setterMethod;

PropertyDefinition() {
modelClass = getDeclaringClass().getEnclosingClass();
// some string mapping that can be driven by annotations for prefixes and so on
this.fieldName = name();
this.getterMethodName = "get"+StringUtils.capFirst(name());
this.setterMethodName = "set"+ StringUtils.capFirst(name());
}

public Field getField() {
...some field resolution
return reflectField;
}

public Method getSetterMethod() {
...some method resolution
return setterMethod;
}

public Method getGetterMethod() {
//...some method resolution
return getterMethod;
}

public Class getModelClass() {return modelClass;}
public String getFieldName() {return fieldName;}

// And then a nice list of helper and delegate methods like,
// They can aggregate annotations from fields and getters/setters methods
public Type getGenericType() {...}
public <t> T getAnnotation(Class<t> annotationClass) {...}
public Annotation[] getAnnotations() {...}
public boolean isAnnotationPresent(Class annotationClass) {...}
public Annotation[] getDeclaredAnnotations() {...}

// Here there is a need to type this
public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException {...}
public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException {...}
}

Then inside my classes I can explicitly, implicit by annotation, or fully implicitly if Java allows it, have:
public class MyModel {
private String firstName;
private String lastName;
private int age;

// No mandatory getters or setters, just the one I want
public String getFirstName() {return firstName;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}

public static enum properties extends PropertyDefinition {
firstName, lastName, age;
}
}

With this ability, java property and binding declaration are possible, readable, don't add any keywords or ugly "->" and becomes very clean and concise. The only draw back is the ''implicit'' or compile time verification between the enum entries name and the actual fields. I keep thinking about this issue and here are my views:
  • I don't want all my fields or methods to be accessible via these XXXDefinition enum,
  • I want to control the fields and methods that children class will expose for binding,
  • The compiler can always do a sanity check.

Using property in Annotations
So now, to solve more binding problems:
  • Associations in JPA. How to you link to the property on the other side in a type safe way?
  • Interceptor Binding. Passing a method reference in an annotation like @PostInsert().
  • And many more...

Since
"abstract enum" are suppose to still be enum they should be able to be used in annotations attributes like:
public @interface OneToMany {
FieldDefinition inverseField() default BaseObject.fields.id;
}

And so now linking properties will be type safe:
public class Person {
@OneToMany(inverseField = Dog.fields.owner)
private Set<Dog> dogs;
}

public class Dog {
@ManyToOne(inverseField = Person.fields.dogs)
private Owner owner;
}

I like it...

11 comments:

Anonymous said...

Excellent article :)
BTW find here another interesting article about Java Enumerations, internationalization and usage in Hibernate

Anonymous said...

http://java.sun.com/docs/books/tutorial/java/javaOO/enum.html
--
Note: All enums implicitly extend java.lang.Enum. Since Java does not support multiple inheritance, an enum cannot extend anything else.
--

Frederic Simon said...

So, if you reached there and you like the language feature of abstract enum:
Vote for the RFE: 6570766

dansoton said...

"http://java.sun.com/docs/books/tutorial/java/javaOO/enum.html
--
Note: All enums implicitly extend java.lang.Enum. Since Java does not support multiple inheritance, an enum cannot extend anything else."

Yes that is true, and the reason Sun gives, but it is a cop out.

Why? Well all classes implicity extend java.lang.Object. But of course you can override this and get a class to extend a different class - since all classes are sub-classes of java.lang.Object and so it doesn't break anything.

That's certainly not deemed multiple-inheritance - of course it's not.

I'd like to see the ability to override the super-class of an enum as long as that super-class extends java.lang.enum (directly or indirectly).

That would mean we could extend enums with extra states and methods etc in the same way as you can extend existing classes.

And it's certainly no more multiple inhertitance than extending any other class.

Anonymous said...

I am still trying to learn the english language, but isn't it wrong to say "you extends"?
It distracts from the topic.

Anuvrat said...

I like the abstract enum idea. I have found myself in this situation lots of time, and the more I try a work around, the more development time I eat off.

imario said...

Just in case someone is interested, I tried to find a solution with the current Java version which is still not able to have "abstract enum" - and documented my findings here http://copy-con.blogspot.com/2011/07/java-properties.html

Not sure if I will use that in one of my projects, but who knows ...

-Michael- said...

Another option, also not currently possible on the JVM but probably more likely in the future, would be to have your enum implement an interface with defender methods defined.

http://cr.openjdk.java.net/~briangoetz/lambda/Defender%20Methods%20v3.pdf

-Michael- said...

Another option, also not possible in today's JVM but more likely to be implemented soon, would be to have your enum implement an interface with defender methods defined.

http://cr.openjdk.java.net/~briangoetz/lambda/Defender%20Methods%20v3.pdf

Xedin Unknown said...

Fred, as far as I can see, the whole problem with enum inheritance is always related to the question of what to do with the constants. But what I don't understand is:
Why not make enums able to extend a non-enum class, and ensure type safety through an interface, i.e. a class that implements an IEnumerable interface will behave as an enum.

I am relatively new to Java programming, but not new at all to programming in general. And so I agree that maybe this question is somewhat naive. The reason is that I did not know that Enums cannot extend; knowing that they may implement an interface, I assumed that they may inherit from a class also. I built a mini-framework in a couple of hours, and when it came to the "cherry on the cake" (an Enum was an entry point for all operations), I found that the behaviour I had provided in abstract classes is unusable in the enum. OMG....

Xedin Unknown said...

Fred, as far as I can see, the whole problem with enum inheritance is always related to the question of what to do with the constants. But what I don't understand is:
Why not make enums able to extend a non-enum class, and ensure type safety through an interface, i.e. a class that implements an IEnumerable interface will behave as an enum.

I am relatively new to Java programming, but not new at all to programming in general. And so I agree that maybe this question is somewhat naive. The reason is that I did not know that Enums cannot extend; knowing that they may implement an interface, I assumed that they may inherit from a class also. I built a mini-framework in a couple of hours, and when it came to the "cherry on the cake" (an Enum was an entry point for all operations), I found that the behaviour I had provided in abstract classes is unusable in the enum. OMG....