Why shouldn't Java enum literals be able to have generic type parameters?

JavaGenericsEnums

Java Problem Overview


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 <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 calling String string = someClass.getValue(LITERAL1)

Java Solutions


Solution 1 - Java

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

Solution 2 - Java

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;
    }

    ...

}

Solution 3 - Java

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.

Solution 4 - Java

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.

Solution 5 - Java

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.

Solution 6 - Java

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 !).

Solution 7 - Java

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.

Solution 8 - Java

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;
    }   
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLukas EderView Question on Stackoverflow
Solution 1 - JavaLukas EderView Answer on Stackoverflow
Solution 2 - JavaMartin AlgestenView Answer on Stackoverflow
Solution 3 - JavaTom Hawtin - tacklineView Answer on Stackoverflow
Solution 4 - Javauser1944408View Answer on Stackoverflow
Solution 5 - Javansfyn55View Answer on Stackoverflow
Solution 6 - JavaChristophe MoineView Answer on Stackoverflow
Solution 7 - JavaIngoView Answer on Stackoverflow
Solution 8 - JavacapsulaView Answer on Stackoverflow