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 getvalue() 方法中的泛型类型参数。编译器可以在调用 String string = someClass.getValue(LITERAL1) 时应用类型转换
enum
变成我们在 Java 1.5 之前使用的“类型安全枚举”习语。突然,您可以将枚举成员参数化。这大概就是我现在要做的。
这已在 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
答案在问题中:
因为类型擦除
这两种方法都不可能,因为参数类型已被删除。
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;
}
...
}
public T convert(Object);
。我猜这个方法可以例如将一堆不同的类型缩小到<T>,例如是一个字符串。 String 对象的构造是运行时的——编译器不做任何事情。因此,您需要知道运行时类型,例如 String.class。还是我错过了什么?
static { FOO = "bar"; }
的 public final static String FOO;
- 即使在编译时已知常量也会被评估。
因为你做不到。严重地。这可以添加到语言规范中。它没有。它会增加一些复杂性。成本效益意味着它不是一个高优先级。
更新:目前正在添加到 JEP 301: Enhanced Enums 下的语言中。
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")
怎么办?由于类型擦除,无法实现该方法以正常工作,例如,当您像 MyEnum.<Int>value("some double property")
一样调用它时抛出异常或返回 null。
MyEnum<?>[] values()
和 MyEnum<?> valueOf(...)
MyEnum<Int> blabla = valueOf("some double property");
那样进行分配,因为类型不兼容。在这种情况下,您还希望获得 null ,因为您想返回 MyEnum<Int>双属性名称不存在,并且由于擦除,您无法使该方法正常工作。
Enum
有很多其他有用的功能,而这个是可选的,也非常有用......
坦率地说,这似乎更像是寻找问题的解决方案。
java enum 的全部目的是对共享相似属性的类型实例的枚举进行建模,以提供超出可比较的 String 或 Integer 表示的一致性和丰富性。
以教科书枚举为例。这不是很有用或一致:
public enum Planet<T>{
Earth<Planet>,
Venus<String>,
Mars<Long>
...etc.
}
为什么我希望我的不同行星有不同的泛型类型转换?它解决了什么问题?它是否证明使语言语义复杂化是合理的?如果我确实需要这种行为,那么枚举是实现它的最佳工具吗?
此外,您将如何管理复杂的转换?
例如
public enum BadIdea<T>{
INSTANCE1<Long>,
INSTANCE2<MyComplexClass>;
}
使用 String
Integer
很容易提供名称或序数。但是泛型将允许您提供任何类型。您将如何管理到 MyComplexClass
的转换?现在,您通过强制编译器知道可以提供给泛型枚举的类型的有限子集并给概念(泛型)引入了额外的混淆,这似乎已经让很多程序员感到困惑,从而混淆了两个构造。
interface Converter<IN, OUT> { OUT convert(IN in); } <E> Set<E> convertListToSet(List<E> in, Converter<? super List<E>, ? extends Set<E>> converter) { return converter.convert(in); }
我们必须手动计算出每次使用哪种类型以及生产哪种类型,并相应地指定界限。
通过使用这个 Java 注释处理器 https://github.com/cmoine/generic-enums,您可以编写类似的代码(convert 方法作为示例实现):
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 类!)。
因为“枚举”是枚举的缩写。它只是一组命名常量,代替序数以使代码更具可读性。
我看不出类型参数化常量的预期含义是什么。
java.lang.String
:public static final Comparator<String> CASE_INSENSITIVE_ORDER
。现在你可以看到了? :-)
CASE_INSENSITIVE_ORDER
完全一样...如果 Comparator<T>
是一个枚举,为什么没有与 <T>
相关联的字面量?
我认为因为基本上枚举不能被实例化
如果 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;
}
}
enum
上的 setO()
方法。我认为枚举常量对我来说意味着不可变。所以即使有可能,我也不会这样做。