这件事现在困扰我一段时间了。我以前曾问过questions,但可能是一个不好的措辞和一个过于抽象的例子。所以我不清楚我究竟在问什么。我会再尝试。请不要妄下结论。我希望这个问题根本不容易回答!
为什么我不能在Java中使用泛型类型参数的枚举?
问题不在于为什么在语法上不可能。我知道它不受支持。问题是:为什么JSR人员“忘记”或“省略”这个非常有用的功能?我无法想象与编译器相关的原因,为什么它不可行。
这就是我喜欢做的事情。这在Java中是可行的。这是创建类型安全枚举的Java 1.4方法:
// A model class for SQL data types and their mapping to Java types
public class DataType<T> implements Serializable, Comparable<DataType<T>> {
private final String name;
private final Class<T> type;
public static final DataType<Integer> INT = new DataType<Integer>("int", Integer.class);
public static final DataType<Integer> INT4 = new DataType<Integer>("int4", Integer.class);
public static final DataType<Integer> INTEGER = new DataType<Integer>("integer", Integer.class);
public static final DataType<Long> BIGINT = new DataType<Long> ("bigint", Long.class);
private DataType(String name, Class<T> 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<T[]> getArrayType() {
// [...]
}
// [ ... more methods ... ]
}
然后,您可以在许多其他地方使用<T>
public class Utility {
// Generic methods...
public static <T> T doStuff(DataType<T> type) {
// [...]
}
}
但是枚举不可能实现这些目的:
// This can't be done
public enum DataType<T> {
// Neither can this...
INT<Integer>("int", Integer.class),
INT4<Integer>("int4", Integer.class),
// [...]
}
现在,正如我所说。我知道这些东西都是按照这种方式设计的。 enum
是句法糖。仿制药也是如此。实际上,编译器完成所有工作并将enums
转换为java.lang.Enum
的子类,将泛型转换为强制转换和合成方法。
但为什么编译器不能进一步允许通用枚举?
修改: 这就是我期望的编译器生成的Java代码:
public class DataType<T> extends Enum<DataType<?>> {
// [...]
}
答案 0 :(得分:4)
我会猜测一下,这是因为Enum类本身的类型参数的协方差问题,定义为Enum<E extends Enum<E>>
,尽管调查所有的那个角落的情况。
除此之外,枚举的主要用例是EnumSet和valueOf之类的东西,你有一组具有不同泛型参数的东西,并从字符串中获取值,所有这些都不支持或更糟糕的泛型参数enum本身。
我知道当我试图用泛音来表达时,我总是处在痛苦的世界里,我想象语言设计师偷看那个深渊并决定不去那里,特别是因为这些功能是同时开发的,对于Enum方面而言,这意味着更多的不确定性。
或者换句话说,在处理本身具有泛型参数的类时,它会遇到Class<T>
的所有问题,并且您必须进行大量的转换和处理原始类型。对于您正在查看的用例类型而言,语言设计者认为并不是真正值得的。
编辑:为了回应评论(和汤姆 - 一个downvote?),嵌套的泛型参数会发生各种不好的事情。 Enum实现了Comparable。如果泛型在竞争中,那么仅仅比较客户端代码中枚举的两个任意元素是行不通的。一旦处理了Generic参数的Generic参数,就会遇到各种各样的边界问题和麻烦。设计一个能够很好地处理它的类很难。在可比较的情况下,我无法找到一种方法来使它能够比较枚举的两个任意成员而不恢复原始类型并获得编译器警告。你能吗?
实际上上面是令人尴尬的错误,因为我在问题中使用DataType作为我的模板来思考这个,但实际上Enum会有一个子类,所以这不太对。
然而,我坚持我的答案的主旨。汤姆提出了EnumSet.complementOf
当然我们仍然有valueOf
会产生问题,而且就Enum的设计本身而言,我们必须意识到这是20/20后见之明的事情。 Enum与仿制药同时设计,没有验证所有此类角落案例的好处。特别是考虑到具有通用参数的Enum的用例相当有限。 (但同样,EnumSet的用例也是如此)。
答案 1 :(得分:1)
我认为不可能有一般化的枚举。如果您可以入侵编译器,您可以拥有通用的Enum子类,并且通用枚举的类文件不会导致问题。
但最后,枚举几乎就是语法糖。在C,C ++,C#中,枚举基本上是int常量的别名。 Java为它提供了更多的功能,但它仍然假设来表示简单的项目。
某些地方的人必须画线。仅仅因为一个类具有枚举实例,并不意味着它必须是一个枚举。如果它在其他领域足够复杂,它应该是一个普通的班级。
在你的情况下,使DataType
成为枚举没有太大的好处。你可以在switch-case中使用enum,这就是它,大不了。 DataType
的非枚举版本运行得很好。
答案 2 :(得分:0)
这就是我的想法 -
常规类有实例。您创建一个类的新实例,将其用于某种目的,然后将其处理掉。例如,List<String>
是一个字符串列表。我可以做任何我想用字符串做的事情然后当我完成后我可以用整数做同样的功能。
对我来说,枚举器不是您创建实例的类型。和单身人士一样。所以我可以看到为什么JAVA不会允许枚举为Enums,因为你真的不能创建一个类型Enum的新实例来像使用类一样临时使用。枚举应该是静态的,并且全局只有一个实例。对我来说,允许泛型用于全局只有一个实例的类是没有意义的。
我希望这会有所帮助。
答案 3 :(得分:0)
我认为您希望使用<T>
参数化枚举的原因归结为能够为枚举的各种常量设置不同的方法签名。
在您的示例中,parse
的签名(参数类型和返回类型)将为:
Datatype.INT
:int parse(String)
Datatype.VARCHAR
:String parse(String)
那么编译器如何能够类似于:
Datatype type = ...
...
int x = type.parse("45");
???
要对这种表达式应用静态类型和类型检查,方法的签名对于所有实例必须相同。但是,最后你建议为不同的实例使用不同的方法签名......这就是为什么用Java完成它的原因。
答案 4 :(得分:-1)
public enum GenericEnum<T> {
SIMPLE, COMPLEX;
public T parse(String s) {
return T.parse(s);
}
}
public void doSomething() {
GenericEnum<Long> longGE = GenericEnum<Long>.SIMPLE;
GenericEnum<Integer> intGE = GenericEnum<Integer>.SIMPLE;
List<Long> longList = new LinkedList<Long>();
List<Integer> intList = new LinkedList<Integer>();
assert(longGE == intGE); // 16
assert(stringList.equals(intList)); // 17
Object x = longGE.parse("1"); // 19
}
第16和17行的断言都是正确的。通用类型在运行时不可用。
枚举的一个优点是你可以使用==来比较它们。第16行的断言将评估为真。
在第19行,我们遇到了一个问题。 longGE和intGE是同一个对象(如第16行的断言所示)。解析(“1”)将返回什么?通用类型信息在运行时不可用。因此,在运行时无法确定解析方法的T值。
枚举基本上是静态的,它们只存在一次。将泛型类型应用于静态类型是没有意义的。
我希望这会有所帮助。
注意 - 这不是工作代码。它使用原始问题中建议的语法。