为什么 Javaenum 文字不能有泛型类型参数?

爪哇枚举是伟大的。仿制药也是。当然,我们都知道后者的局限性,因为类型擦除。但有一件事我不明白,为什么我不能创建这样的枚举:

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>的附加属性,同时,作为枚举(不可变、单例、可枚举等等)

问题:

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

谁能给我解释一下? 在你回答之前,想想这个:

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

我想是因为基本上 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;
}
}

答案就在这个问题里:

因为类型擦除

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

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


...


}

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

更新: 目前正在 JEP 301: 增强枚举下添加到该语言。

因为“ enum”是枚举的缩写。 它只是一组代替序数的命名常量,以使代码更易于阅读。

我不明白类型参数化常量的本意是什么。

坦率地说,这看起来更像是一个寻找问题的解决方案,而不是任何东西。

Java 枚举的全部用途是对共享相似属性的类型实例的枚举进行建模,以提供超出可比 String 或 Integer 表示的一致性和丰富性。

以一个教科书枚举为例,这并不是非常有用或一致的:

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

为什么我希望我不同的行星有不同的通用类型转换?它能解决什么问题?它是否证明了语言语义复杂化的合理性?如果我确实需要这种行为,枚举是实现它的最佳工具吗?

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

例如:

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

它很容易与 String Integer提供的名称或序数。但是泛型允许您提供任何类型。您将如何管理到 MyComplexClass的转换?现在,通过强制编译器知道可以提供给泛型枚举的类型的有限子集,以及对概念(泛型)引入额外的混淆,这两个结构已经让很多程序员感到困惑,从而混淆了这两个结构。

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

MyEnum.valueOf(String name)呢?

如果您认为编译器可以将泛型方法设置为

Public static MyEnum valueOf (String name) ;

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

很遗憾,这个问题在 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

通过使用这个 Java 注释处理器 https://github.com/cmoine/generic-enums,您可以编写类似下面这样的代码:

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

注释处理器将生成一个枚举 MyEnumExt(可定制) ,它克服了 java 枚举的限制。相反,它生成一个可以精确地作为枚举使用的 Java 类(最后,枚举被编译成实现 Enum的 Java 类).