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 <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
The same applies to the generic type parameter in the T getvalue() method. The compiler can apply type casting when calling String string = someClass.getValue(LITERAL1)
enum
into a "typesafe enum" idiom we used before Java 1.5. Suddenly, you can have your enum members parameterized. That's probably what I'm going to do now.
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;
}
...
}
public T convert(Object);
. I'm guessing this method could for instance narrow a bunch of different types to <T>, which for instance is a String. The construction of the String object is runtime - nothing the compiler does. Hence you need to know the runtime type, such as String.class. Or am I missing something?
public final static String FOO;
with a static block static { FOO = "bar"; }
- also constants are evaluated even when known at compile time.
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.
MyEnum<?>[] values()
and MyEnum<?> valueOf(...)
MyEnum<Int> blabla = valueOf("some double property");
because the types are not compatible. Also you want in that case to get null because you want to return MyEnum<Int> which does not exists for double property name and you cannot make that method work properly because of erasure.
Enum
has a lot of other useful features, and this one would be optional and very useful as well...
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.
interface Converter<IN, OUT> { OUT convert(IN in); } <E> Set<E> convertListToSet(List<E> in, Converter<? super List<E>, ? extends Set<E>> converter) { return converter.convert(in); }
We have to manually work out each time which type is consumed and which produced, and specify the bounds accordingly.
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
!).
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.
java.lang.String
: public static final Comparator<String> CASE_INSENSITIVE_ORDER
. You see now? :-)
CASE_INSENSITIVE_ORDER
... If Comparator<T>
were an enum, why not have literals with any associated bound for <T>
?
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;
}
}
setO()
method on an enum
. I consider enums constants and to me that implies immutable. So even if it's possible, I wouldn't do it.
Success story sharing