162
votes

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)
8
I don't understand this limitation either. I came across this recently where my enum contained different "Comparable" types, and with generics, only Comparable types of the same types can be compared without warnings that need to be suppressed (even though at runtime the proper ones would be compared). I could have gotten rid of these warnings by using a type bound in the enum to specify which comparable type was supported, but instead I had to add the SuppressWarnings annotation - no way around it! Since compareTo does throw a class cast exception anyways, it's ok I guess, but still...MetroidFan2002
(+1) I'm just in the middle of trying to close a type safety gap in my project, stopped dead by this arbitrary limitation. Just consider this: turn the 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.Marko Topolnik
@EdwinDalorzo: Updated the question with a concrete example from jOOQ, where this would have been immensely useful in the past.Lukas Eder
@LukasEder I see your point now. Looks like a cool a new feature. Maybe you should suggest it in project coin mailing list I see other interesting proposals there on enums, but not one like yours.Edwin Dalorzo
Totally agree. Enum without generics are crippled. Your case #1 is also mine. If I need generic enum I give up JDK5 and implement it in plain old Java 1.4 style. This approach has also additional benefit: I'm not forced to have all constants in one class or even package. Thus, package-by-feature style is accomplished a lot better. This is perfect just for configuration-like "enums" - constants are spread in packages according to their logical meaning (and if I wish to see them all, I have type hieararchy shown).Tomáš Záluský

8 Answers

61
votes

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

12
votes

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

    ...

}
5
votes

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.

4
votes

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.

0
votes

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.

0
votes

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

-2
votes

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.

-3
votes

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