Why shouldn't Java enum literals be able to have generic type parameters?
Java enums are great. So are generics. Of course we all know the limitations of the latter because of type erasure. But there is one thing I don't understand, Why can't I create an enum like this:
public enum MyEnum<T> {
LITERAL1<String>,
LITERAL2<Integer>,
LITERAL3<Object>;
}
This generic type parameter <T>
in turn could then be useful in various places. Imagine a generic type parameter to a method:
public <T> T getValue(MyEnum<T> param);
Or even in the enum class itself:
public T convert(Object o);
More concrete example #1
Since the above example might seem too abstract for some, here's a more real-life example of why I want to do this. In this example I want to use
- Enums, because then I can enumerate a finite set of property keys
- Generics, because then I can have method-level type-safety for storing properties
public interface MyProperties {
public <T> void put(MyEnum<T> key, T value);
public <T> T get(MyEnum<T> key);
}
More concrete example #2
I have an enumeration of data types:
public interface DataType<T> {}
public enum SQLDataType<T> implements DataType<T> {
TINYINT<Byte>,
SMALLINT<Short>,
INT<Integer>,
BIGINT<Long>,
CLOB<String>,
VARCHAR<String>,
...
}
Each enum literal would obviously have additional properties based on the generic type &l开发者_Go百科t;T>
, while at the same time, being an enum (immutable, singleton, enumerable, etc. etc.)
Question:
Did no one think of this? Is this a compiler-related limitation? Considering the fact, that the keyword "enum" is implemented as syntactic sugar, representing generated code to the JVM, I don't understand this limitation.
Who can explain this to me? Before you answer, consider this:
- I know generic types are erased :-)
- I know there are workarounds using Class objects. They're workarounds.
- Generic types result in compiler-generated type casts wherever applicable (e.g. when calling the convert() method
- The generic type <T> would be on the enum. Hence it is bound by each of the enum's literals. Hence the compiler would know, which type to apply when writing something like
String string = LITERAL1.convert(myObject); Integer integer = LITERAL2.convert(myObject);
- The same applies to the generic type parameter in the
T getvalue()
method. The compiler can apply type casting when callingString string = someClass.getValue(LITERAL1)
This has been discussed as of JEP-301 Enhanced Enums, which was withdrawn, regrettably. The example given in the JEP is, which is precisely what I was looking for:
enum Argument<X> { // declares generic enum
STRING<String>(String.class),
INTEGER<Integer>(Integer.class), ... ;
Class<X> clazz;
Argument(Class<X> clazz) { this.clazz = clazz; }
Class<X> getClazz() { return clazz; }
}
Class<String> cs = Argument.STRING.getClazz(); //uses sharper typing of enum constant
Unfortunately, the JEP was struggling with significant issues, which couldn't be resolved: http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-May/000041.html
The answer is in the question:
because of type erasure
None of these two methods are possible, since the argument type is erased.
public <T> T getValue(MyEnum<T> param);
public T convert(Object);
To realise those methods you could however construct your enum as:
public enum MyEnum {
LITERAL1(String.class),
LITERAL2(Integer.class),
LITERAL3(Object.class);
private Class<?> clazz;
private MyEnum(Class<?> clazz) {
this.clazz = clazz;
}
...
}
Because you can't. Seriously. That could be added to the language spec. It hasn't been. It would add some complexity. That benefit to cost means it isn't a high priority.
Update: Currently being added to the language under JEP 301: Enhanced Enums.
There are other methods in ENUM that wouldn't work. What would MyEnum.values()
return?
What about MyEnum.valueOf(String name)
?
For the valueOf if you think that compiler could make generic method like
public static MyEnum valueOf(String name);
in order to call it like MyEnum<String> myStringEnum = MyEnum.value("some string property")
, that wouldn't work either.
For example what if you call MyEnum<Int> myIntEnum = MyEnum.<Int>value("some string property")
?
It is not possible to implement that method to work correctly, for example to throw exception or return null when you call it like MyEnum.<Int>value("some double property")
because of type erasure.
By using this Java annotation processor https://github.com/cmoine/generic-enums, you can write something like (the convert method were implemented as an example):
import org.cmoine.genericEnums.GenericEnum;
import org.cmoine.genericEnums.GenericEnumParam;
@GenericEnum
public enum MyEnum {
LITERAL1(String.class) {
@Override
@GenericEnumParam
public Object convert(Object o) {
return o.toString(); // example
}
},
LITERAL2(Integer.class) {
@Override
@GenericEnumParam
public Object convert(Object o) {
return o.hashCode(); // example
}
},
LITERAL3(Object.class) {
@Override
@GenericEnumParam
public Object convert(Object o) {
return o; // example
}
};
MyEnum(Class<?> clazz) {
}
@GenericEnumParam
public abstract Object convert(Object o);
}
The annotation processor will generate an enum MyEnumExt
(customizable) which overcomes the limitation of java enums. Instead, it generates a Java class usable exactly as an enum (in the end, an enum is compiled into a Java class implementing Enum
!).
Frankly this seems like more of a solution in search of a problem than anything.
The entire purpose of the java enum is to model a enumeration of type instances that share similiar properties in a way that provides consistency and richness beyond that of comparable String or Integer representations.
Take an example of a text book enum. This is not very useful or consistent:
public enum Planet<T>{
Earth<Planet>,
Venus<String>,
Mars<Long>
...etc.
}
Why would I want my different planets to have different generic type conversions? What problem does it solve? Does it justify complicating the language semantics? If I do need this behavior is an enum the best tool to achieve it?
Additionally how would you manage complex conversions?
for Instance
public enum BadIdea<T>{
INSTANCE1<Long>,
INSTANCE2<MyComplexClass>;
}
Its easy enough with String
Integer
to supply the name or ordinal. But generics would allow you to supply any type. How would you manage the conversion to MyComplexClass
? Now your mucking up two constructs by forcing the compiler to know that there are a limited subset of types that can be supplied to generic enums and introducing additional confusion to concept(Generics) that already seems elude a lot of programmers.
I think because basically Enums can't be instanced
Where would you set the T class, if JVM allowed you to do so?
Enumeration is data that is supposed to be always the same, or at least, that it won't change dinamically.
new MyEnum<>()?
Still the following approach may be useful
public enum MyEnum{
LITERAL1("s"),
LITERAL2("a"),
LITERAL3(2);
private Object o;
private MyEnum(Object o) {
this.o = o;
}
public Object getO() {
return o;
}
public void setO(Object o) {
this.o = o;
}
}
Becasue "enum" is the abbreviation for enumeration. It's just a set of named constants that stand in place of ordinal numbers to make the code better readable.
I don't see what the intended meaning of a type-parameterized constant could be.
精彩评论