Java枚举和generics

这件事现在困扰我一段时间了。 我以前曾问过问题 ,但可能是一个不好的措辞和一个过于抽象的例子。 所以我不清楚我究竟在问什么。 我会再尝试。 请不要妄下结论。 我希望这个问题根本不容易回答!

为什么我不能在Java中使用generics类型参数的枚举?

问题不在于为什么它在语法上是不可能的。 我知道它不受支持。 问题是:为什么JSR人员“忘记”或“省略”这个非常有用的function? 我无法想象与编译器相关的原因,为什么它不可行。

这就是我喜欢做的事情。 这在Java中是可行的。 这是创建类型安全枚举的Java 1.4方法:

// A model class for SQL data types and their mapping to Java types public class DataType implements Serializable, Comparable<DataType> { private final String name; private final Class type; public static final DataType INT = new DataType("int", Integer.class); public static final DataType INT4 = new DataType("int4", Integer.class); public static final DataType INTEGER = new DataType("integer", Integer.class); public static final DataType BIGINT = new DataType ("bigint", Long.class); private DataType(String name, Class type) { this.name = name; this.type = type; } // Returns T. I find this often very useful! public T parse(String string) throws Exception { // [...] } // Check this out. Advanced generics: public T[] parseArray(String string) throws Exception { // [...] } // Even more advanced: public DataType getArrayType() { // [...] } // [ ... more methods ... ] } 

然后,您可以在许多其他地方使用

 public class Utility { // Generic methods... public static  T doStuff(DataType type) { // [...] } } 

但这些东西不可能用枚举:

 // This can't be done public enum DataType { // Neither can this... INT("int", Integer.class), INT4("int4", Integer.class), // [...] } 

现在,正如我所说。 我知道这些东西都是这样设计的。 enum是句法糖。 仿制药也是如此。 实际上,编译器完成所有工作并将enums转换为java.lang.Enum和generics的子类到转换和合成方法。

但为什么编译器不能进一步允许通用枚举?

编辑 :这是我期望的编译器生成的Java代码:

 public class DataType extends Enum<DataType> { // [...] } 

我会猜测一下,这是因为Enum类本身的类型参数的协方差问题,定义为Enum> ,尽管调查所有内容有点大那个角落的情况。

除此之外,枚举的主要用例是EnumSet和valueOf之类的东西,你有一组具有不同generics参数的东西,并从字符串中获取值,所有这些都不支持或更糟糕的枚举本身的generics参数。

我知道当我试图用泛音来表达时,我总是处在一个痛苦的世界里,我想象语言设计师偷看那个深渊并决定不去那里,特别是因为这些特征同时发展,这意味着甚至Enum方面的不确定性更多。

或者换句话说,在处理本身具有generics参数的Class ,它将具有Class所有问题,并且您将不得不进行大量的转换和处理原始类型。 对于您正在查看的用例类型而言,语言设计者认为并不值得。

编辑:响应评论(和汤姆 – 一个downvote?),嵌套的generics参数会发生各种不好的事情。 Enum实现了Comparable。 如果generics在竞争中,那么在客户端代码中比较枚举的两个任意元素根本不起作用。 一旦处理了Generic参数的Generic参数,就会遇到各种各样的边界问题和麻烦。 设计一个能很好地处理它的类很难。 在可比较的情况下,我无法找到一种方法来使它能够比较枚举的两个任意成员而不恢复原始类型并获得编译器警告。 您可以…吗?

实际上上面是令人尴尬的错误,因为我在问题中使用DataType作为我的模板来思考这个,但实际上Enum会有一个子类,所以这不太对。

但是,我坚持我的答案的主旨。 Tom提出了EnumSet.complementOf ,当然我们仍然有EnumSet.complementOf产生问题,并且在Enum的设计EnumSet.complementOf的程度上,我们必须意识到这是一个20/20后见之明的事情。 Enum与仿制药同时设计,并没有validation所有此类角落案例的好处。 特别是考虑到具有通用参数的Enum的用例相当有限。 (但是再一次,EnumSet的用例也是如此)。

我认为不可能有一般化的枚举。 如果您可以入侵编译器,您可以拥有通用的Enum子类,并且通用枚举的类文件不会导致问题。

但最后,enum几乎是一种语法糖。 在C,C ++,C#中,枚举基本上是int常量的别名。 Java赋予它更多的function,但它仍然应该代表简单的项目。

在某个地方人们不得不画线。 仅仅因为一个类具有枚举实例,并不意味着它必须是一个枚举。 如果它在其他领域足够复杂,它应该是一个普通的课程。

在您的情况下,使DataType成为枚举没有太大的好处。 你可以在switch-case中使用枚举,这就是它,大不了。 DataType的非枚举版本工作得很好。

这就是我的想法 –

常规类有实例。 您创建一个类的新实例,将其用于某种目的,然后将其处理掉。 例如, List是一个字符串列表。 我可以做任何我想用字符串做的事情然后当我完成后我可以用整数做同样的function。

对我来说,枚举器不是您创建实例的类型。 和单身人士一样。 所以我可以看到为什么JAVA不允许枚举为Enums,因为你真的不能创建一个类型Enum的新实例来像使用类一样临时使用。 枚举应该是静态的,并且全局只有一个实例。 对我来说,允许generics只用于全局只有一个实例的类是没有意义的。

我希望这有帮助。

我认为你希望用参数化枚举的原因可以归结为能够为枚举的各种常量设置不同的方法签名。

在您的示例中, parse的签名(参数类型和返回类型)将是:

  • for Datatype.INT int parse(String)
  • for Datatype.VARCHARString parse(String)
  • 等等

那么编译器如何能够类似于以下内容:

 Datatype type = ... ... int x = type.parse("45"); 

???

要对这种表达式应用静态类型和类型检查,方法的签名对于所有实例必须相同 。 但是,最后你建议为不同的实例使用不同的方法签名……这就是为什么用Java完成它的原因。

 public enum GenericEnum { SIMPLE, COMPLEX; public T parse(String s) { return T.parse(s); } } public void doSomething() { GenericEnum longGE = GenericEnum.SIMPLE; GenericEnum intGE = GenericEnum.SIMPLE; List longList = new LinkedList(); List intList = new LinkedList(); assert(longGE == intGE); // 16 assert(stringList.equals(intList)); // 17 Object x = longGE.parse("1"); // 19 } 

第16和17行的断言都是正确的。 通用类型在运行时不可用。

枚举的一个优点是你可以使用==来比较它们。 第16行的断言将评估为真。

在第19行,我们遇到了一个问题。 longGE和intGE是同一个对象(如第16行的断言所示)。解析(“1”)将返回什么? 通用类型信息在运行时不可用。 因此,在运行时无法确定解析方法的T.

枚举基本上是静态的,它们只存在一次。 将generics类型应用于静态类型是没有意义的。

我希望这有帮助。

注意 – 这不是工作代码。 它使用原始问题中建议的语法。