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

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,我不明白这个限制 .

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

  • 我知道通用类型被删除了:-)

  • 我知道有使用Class对象的变通方法 . 他们是变通办法 .

  • 通用类型会在适用的情况下导致编译器生成的类型转换(例如,在调用convert()方法时)

  • 泛型类型<T>将在枚举上 . 因此它受每个枚举文字的约束 . 因此编译器会知道,在编写像 String string = LITERAL1.convert(myObject); Integer integer = LITERAL2.convert(myObject); 这样的东西时要应用哪种类型

  • 这同样适用于 T getvalue() 方法中的泛型类型参数 . 调用 String string = someClass.getValue(LITERAL1) 时,编译器可以应用类型转换

回答(7)

3 years ago

现在正在讨论JEP-301 Enhanced Enums . 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

3 years ago

答案在于问题:

因为类型擦除

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

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

    ...

}

3 years ago

ENUM中还有其他方法无效 . MyEnum.values() 将返回什么?

MyEnum.valueOf(String name) 怎么样?

对于valueOf,如果您认为编译器可以使泛型方法如此

public static MyEnum valueOf(String name);

为了像 MyEnum<String> myStringEnum = MyEnum.value("some string property") 那样调用它,这也无济于事 . 例如,如果你调用 MyEnum<Int> myIntEnum = MyEnum.<Int>value("some string property") 怎么办?不可能实现该方法正常工作,例如抛出异常或在调用它时返回null,因为类型擦除,如 MyEnum.<Int>value("some double property") .

3 years ago

因为你做不到 . 认真 . 这可以添加到语言规范中 . 它一直没有 . 这会增加一些复杂性 . 成本的好处意味着它不是一个高优先级 .

更新:目前正在添加到JEP 301: Enhanced Enums下的语言 .

3 years ago

坦率地说,这似乎更像是一个解决问题的解决方案 .

java enum的全部目的是为类型实例的枚举建模,这些类型实例共享相似的属性,提供超出可比较的String或Integer表示的一致性和丰富性 .

举一个教科书枚举的例子 . 这不是非常有用或一致:

public enum Planet<T>{
    Earth<Planet>,
    Venus<String>,
    Mars<Long>
    ...etc.
}

为什么我希望不同的行星具有不同的泛型类型转换?它解决了什么问题?它是否有理由使语言语义复杂化?如果我确实需要这种行为,那么enum是实现它的最佳工具吗?

另外,您如何管理复杂的转化?

例如

public enum BadIdea<T>{
   INSTANCE1<Long>,
   INSTANCE2<MyComplexClass>;
}

它很容易用 String Integer 来提供名称或序号 . 但是泛型可以让你提供任何类型 . 您如何管理转换为 MyComplexClass ?现在,通过强制编译器知道有一些有限的类型子集可以提供给通用枚举并引入额外的概念(泛型),这已经让许多程序员无法实现,从而破坏了两个构造 .

3 years ago

Becasue“enum”是枚举的缩写 . 它只是一组命名常量代替序数数字使代码更易读 .

我没有看到类型参数化常量的预期含义是什么 .

3 years ago

我认为因为基本上Enums不能被实例化

如果JVM允许你这样做,你会在哪里设置T类?

枚举是应该始终相同的数据,或者至少是它不会在动态上改变的数据 .

新的MyEnum <>()?

以下方法仍然有用

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