为什么 Java 枚举文本不能具有泛型类型参数?

2022-08-31 07:05:49

Java枚举很棒。泛型也是如此。当然,由于类型擦除,我们都知道后者的局限性。但有一件事我不明白,为什么我不能创建这样的枚举:

public enum MyEnum<T> {
    LITERAL1<String>,
    LITERAL2<Integer>,
    LITERAL3<Object>;
}

反过来,此泛型类型参数在各个位置可能有用。想象一个方法的泛型类型参数:<T>

public <T> T getValue(MyEnum<T> param);

甚至在枚举类本身中:

public T convert(Object o);

更具体的例子#1

由于上面的例子对某些人来说可能看起来太抽象了,这里有一个更现实的例子来说明我为什么要这样做。在这个例子中,我想使用

  • 枚举,因为这样我就可以枚举一组有限的属性键
  • 泛型,因为这样我就可以使用方法级类型安全性来存储属性
public interface MyProperties {
     public <T> void put(MyEnum<T> key, T value);
     public <T> T get(MyEnum<T> key);
}

更具体的例子#2

我有一个数据类型的枚举:

public interface DataType<T> {}

public enum SQLDataType<T> implements DataType<T> {
    TINYINT<Byte>,
    SMALLINT<Short>,
    INT<Integer>,
    BIGINT<Long>,
    CLOB<String>,
    VARCHAR<String>,
    ...
}

每个枚举文本显然将具有基于泛型类型的附加属性,同时是枚举(不可变,单例,枚举等)。<T>

问题:

难道没有人想到这一点吗?这是与编译器相关的限制吗?考虑到关键字“enum”被实现为语法糖的事实,表示生成的代码到JVM,我不理解这个限制。

谁能向我解释这一点?在回答之前,请考虑以下事项:

  • 我知道泛型类型被删除了:-)
  • 我知道使用类对象有解决方法。它们是解决方法。
  • 泛型类型在适用的情况下导致编译器生成的类型转换(例如,在调用 convert() 方法时
  • 泛型类型 <T> 将在枚举中。因此,它受每个枚举的文字的约束。因此,编译器会知道在编写类似内容时要应用哪种类型String string = LITERAL1.convert(myObject); Integer integer = LITERAL2.convert(myObject);
  • 这同样适用于方法中的泛型类型参数。编译器可以在调用时应用类型转换T getvalue()String string = someClass.getValue(LITERAL1)

答案 1

这已经讨论过JEP-301增强枚举,遗憾的是,它被撤回了。JEP中给出的例子是,这正是我正在寻找的:

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

不幸的是,JEP正在努力解决无法解决的重大问题:http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-May/000041.html


答案 2

答案就在问题中:

由于类型擦除

这两种方法都不可能,因为参数类型被擦除。

public <T> T getValue(MyEnum<T> param);
public T convert(Object);

然而,要实现这些方法,你可以将枚举构造为:

public enum MyEnum {
    LITERAL1(String.class),
    LITERAL2(Integer.class),
    LITERAL3(Object.class);

    private Class<?> clazz;

    private MyEnum(Class<?> clazz) {
      this.clazz = clazz;
    }

    ...

}