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.
精彩评论