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

Luk*_*der 139 java generics enums

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

public enum MyEnum<T> {
    LITERAL1<String>,
    LITERAL2<Integer>,
    LITERAL3<Object>;
}
Run Code Online (Sandbox Code Playgroud)

这个泛型类型参数<T>反过来可以在各个地方使用.想象一下方法的泛型类型参数:

public <T> T getValue(MyEnum<T> param);
Run Code Online (Sandbox Code Playgroud)

甚至在枚举类中:

public T convert(Object o);
Run Code Online (Sandbox Code Playgroud)

更具体的例子#1

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

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

更具体的例子#2

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

public interface DataType<T> {}

public enum SQLDataType<T> implements DataType<T> {
    TINYINT<Byte>,
    SMALLINT<Short>,
    INT<Integer>,
    BIGINT<Long>,
    CLOB<String>,
    VARCHAR<String>,
    ...
}
Run Code Online (Sandbox Code Playgroud)

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

题:

没有人想到这个吗?这是与编译器相关的限制吗?考虑到事实,关键字" 枚举 "是作为语法糖实现的,表示生成的代码到JVM,我不明白这个限制.

谁能向我解释一下?在你回答之前,考虑一下:

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

Luk*_*der 48

现在正在讨论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
Run Code Online (Sandbox Code Playgroud)

不幸的是,JEP仍在努力应对重大问题:http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-May/000041.html

  • 截至 2018 年 12 月,有一些 [JEP 301 周围再次出现生命迹象](http://mail.openjdk.java.net/pipermail/amber-spec-experts/2018-December/000876.html),但略过这一点讨论清楚地表明,问题还远未解决。 (3认同)
  • 此 JEP 已于 2020 年 9 月撤回:[撤回评论](https://bugs.openjdk.java.net/browse/JDK-8170351?focusedCommentId=14371268&amp;page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel #评论-14371268) (2认同)

Mar*_*ten 11

答案在于问题:

因为类型擦除

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

public <T> T getValue(MyEnum<T> param);
public T convert(Object);
Run Code Online (Sandbox Code Playgroud)

要实现这些方法,您可以构建枚举为:

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

    private Class<?> clazz;

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

    ...

}
Run Code Online (Sandbox Code Playgroud)

  • 我认为你错过了泛型类型<T>在枚举(或其生成的类)上的事实.枚举的唯一实例是它的文字,它们都提供了一个常量的泛型类型绑定.因此,在公共T转换(对象)中没有关于T的歧义; (5认同)
  • 嗯,擦除发生在编译时.但编译器可以使用泛型类型信息进行类型检查.然后将泛型类型"转换"为类型转换.我会改写这个问题 (2认同)
  • 不确定我完全跟随.拿`public T convert(Object);`.我猜这个方法可以将一堆不同的类型缩小到<T>,例如它是一个String.String对象的构造是运行时 - 编译器没有.因此,您需要知道运行时类型,例如String.class.或者我错过了什么? (2认同)
  • 啊哈!知道了。你比我聪明 :) (2认同)

Tom*_*ine 5

因为你不能。说真的 可以将其添加到语言规范中。还没有。这会增加一些复杂性。成本节约意味着它不是一个高度优先事项。

更新:当前已添加到JEP 301:增强枚举下的语言中。

  • 我已经考虑了几天了,但仍然没有发现任何并发症。 (3认同)

use*_*408 5

ENUM 中还有其他方法不起作用。会返回什么MyEnum.values()

关于什么MyEnum.valueOf(String name)

对于 valueOf 如果您认为编译器可以创建像这样的泛型方法

公共静态MyEnum valueOf(字符串名称);

为了像这样称呼它MyEnum<String> myStringEnum = MyEnum.value("some string property"),那也行不通。例如,如果您打电话怎么办MyEnum<Int> myIntEnum = MyEnum.<Int>value("some string property")?不可能实现该方法正确工作,例如,当您调用它时,MyEnum.<Int>value("some double property")由于类型擦除而抛出异常或返回 null。

  • 为什么他们不工作?他们只需使用通配符...:`MyEnum&lt;?&gt;[]values()`和`MyEnum&lt;?&gt;valueOf(...)` (2认同)