这件事暂时让我不安。我以前问过问题,但可能用了一个不好的措词和一个太抽象的例子。所以不清楚我到底在问什么。我再试一次。请不要妄下结论。我想这个问题根本不容易回答!
为什么我不能用Java中的泛型类型参数枚举呢?
问题不在于它为什么不可能,句法上。我知道这是不受支持的。问题是:为什么JSR人员"忘记"或"忽略"了这个非常有用的特性?我无法想象与编译器相关的原因,为什么它不可行。
这就是我想做的。这在Java中是可能的。Java 1.4的方式创建类型安全枚举:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| // 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 ... ]
} |
然后,您可以在许多其他地方使用。
1 2 3 4 5 6 7
| public class Utility {
// Generic methods...
public static <T> T doStuff(DataType<T> type) {
// [...]
}
} |
但是这些事情对于枚举是不可能的:
1 2 3 4 5 6 7 8 9
| // 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的子类,将泛型转换为casts和合成方法。
但是为什么编译器不能进一步考虑通用枚举呢??
编辑:这就是我所期望的编译器生成的Java代码:
1 2 3
| public class DataType<T> extends Enum<DataType<?>> {
// [...]
} |
- JavaEnm文字为什么不能具有泛型类型参数的可能副本?
- 嗯,我说我又问了这个问题,但措辞更清楚了,因为另一个问题给了我错误的答案(在我看来)。我更喜欢关闭另一个…
- @所以,改进原来的问题。看起来再问这个问题没什么帮助。
- 这不是一个可行性的问题,而是一个在部署中究竟会破坏什么的问题?另外,为什么您需要这个功能?
- 我以前在StackOverflow上做过这个,然后我被批评并被要求提出一个新的问题…你们能决定吗?:)
- @为什么?我认为这个例子展示了一个非常好的用例…?我敢肯定这是可能的。
- @卢卡斯,因为甲骨文不想激怒很多客户?C!=爪哇
- @ WOT4MOO。这不仅仅是甲骨文的工作。是的,他们每天都用PL/SQL来激怒我。所以也许他们也开始用Java做这件事)。还有C!= Java…哦…好吧…
- @卢卡斯·埃德尔,你的例子毫无意义,我向你说明了原因。
- 毕奇洛普。为什么没有意义?我认为你不理解泛型在Java中是如何工作的…
- @卢卡斯·埃德尔,我告诉过你原因。如果你不理解我的回答,你可以要求更多的澄清。尽管如果你更喜欢侮辱别人而不是不屈不挠,那也没关系。
- @比奇克劳普,对不起,有个误会。我不是有意侮辱。请澄清
我想猜测一下,这是因为枚举类本身的类型参数上的协方差问题,它被定义为Enum>,尽管调查这类的所有角情况有点困难。
除此之外,枚举的一个主要用例是使用诸如enumset和value of之类的东西,其中有一组具有不同泛型参数的东西,并从字符串中获取值,所有这些都不支持枚举本身的泛型参数,或者更糟。
我知道,当我试图用仿制药获得这种幻想时,我总是处于痛苦的世界中,我想象语言设计者窥视了那个深渊,决定不去那里,特别是因为这些特性是同时开发的,这意味着对事物的枚举方面的不确定性更大。
或者换一种方式,在处理那些本身具有泛型参数的类时,它会有Class的所有问题,并且您需要进行大量的转换和处理原始类型。对于您正在查看的用例类型,语言设计者认为不值得这样做。
edit:in response to the comments(and tom-a downvote?),嵌套的泛型参数会发生各种错误。枚举实现可比较。如果发挥了泛型的作用,在客户机代码中比较枚举的两个任意元素是行不通的。一旦你处理了一个通用参数的通用参数,你最终会遇到各种边界问题和头疼。很难设计一个处理好它的类。在可比较的情况下,如果不恢复为原始类型并获得编译器警告,我就无法找到一种方法使比较枚举的两个任意成员有效。你能?< /打击>
实际上,上面的错误是令人难堪的,因为我使用问题中的数据类型作为考虑这个问题的模板,但实际上枚举将具有子类,所以这并不完全正确。
然而,我坚持我的回答要点。汤姆提出了EnumSet.complementOf,当然,我们还有产生问题的valueOf,在某种程度上,枚举的设计可以起作用,我们必须认识到这是事后诸葛亮的事。枚举是与泛型同时设计的,没有验证所有这些角点案例的好处。特别是考虑到具有泛型参数的枚举的用例是相当有限的。(但同样,Enumset的用例也是如此)。
- +1个非常好的答案。我几乎不使用EnumSet。我可以看到,它们会妨碍我的泛型类型。
- 一个奇怪的例子(涉及类是泛型的)是Enum.ValueOf方法。它的第一个参数应该是枚举类。但现在您遇到了一个问题:如果您将class传递给它,您将收到一个原始类型警告,并且不能将class>(如果您可以以类型安全的方式获得它,这意味着什么呢?).
- 我看不到任何有意义的东西。
- 我又想到了这个。生成到public class DataType extends Enum>的public enum DataType是错误的,因为可以针对每个实例进行绑定,而Enum不支持这种绑定。不过,这可能会奏效:public class DataType extends Enum>。我认为当E与DataType>绑定时,EnumSet没有问题。
- 以东十一〔11〕对我来说很好。至少有那么远。虽然我们也有EnumSet>,但我们将有EnumSet>半实用性。不幸的是,当我们使用EnumSet.complementOf或EnumSet.range时,最后一个问题变得有点问题。
- @汤姆和卢卡斯,我觉得你们对这件事想得太多了。试着让比较器工作。我将更新我的答案,以扩展一些进一步的想法。
- @宜沙江东一〔16〕才行。实现Comparable的enum类(使用finalcompareTo方法)不需要更改。
- @汤姆,我花了点时间想明白你怎么能这么说,然后我明白了。您正在讨论实现本身。我不是,我说的是把它称为客户。我会更新我的答案来澄清。
- @你是说,如果你使用的是Collections.max,比如说,你需要一个Collection>,而不是Collection>。是的。这重要吗?(当然,如果你真的出于某种原因想写你自己的Comparator>)。
- 我错过了一些东西…我猜你们没走多远,只是不知道为什么Enums不是这样设计的…也许我们永远不会知道:祝你周末愉快!
我认为不可能有遗传枚举。如果您可以入侵编译器,那么您可以拥有一个枚举的通用子类,并且您的通用枚举的类文件不会导致问题。
但归根结底,枚举在很大程度上是一种语法糖。在C、C++、C语言中,枚举基本上是int常数的别名。Java赋予了它更多的能量,但它仍然代表简单的项目。
在某个地方人们必须划出界线。仅仅因为类有枚举实例,并不意味着它必须是枚举。如果它在其他领域足够成熟,它应该是一个正规的班级。
在您的情况下,使DataType枚举没有太大的优势。你可以在交换的情况下使用枚举,这是很重要的。DataType的非枚举验证工作正常。
- 我明白你的意思。但实施equals()、hashCode()、toString()、Serializable、Comparable、values()、valueOf()等措施,对enum也是一大好处。这通常是我选择enum而不仅仅是switch-case声明的原因。
我认为您希望使用参数化枚举的原因归结为能够为枚举的各种常量使用不同的方法签名。
在您的示例中,parse的签名(参数类型和返回类型)为:
- 对于Datatype.INT:int parse(String)。
- 对于Datatype.VARCHAR:String parse(String)。
- 等等
那么,编译器如何能够对如下内容进行类型检查:
1 2 3
| Datatype type = ...
...
int x = type.parse("45"); |
????
要对此类表达式应用静态类型和类型检查,方法的签名对于所有实例都必须相同。但是,最后您建议对不同的实例使用不同的方法签名…这就是为什么在Java中不可能做到这一点的原因。
- 那么告诉我,在实现类型安全枚举的1.4方法中,这是如何可能的(是的,这是可能的),但在使用enum时,这是不可能的?
- 注意:编译器对您的示例执行此操作:int x = ((Integer) type.parse("45")).intValue()。
- 在1.4中,您有不同类型的"常量"(实际上是实例):int的类型是datatype,varchar的类型是datatype等等。这些类型彼此不兼容。这与枚举的定义(理解用例)不兼容,对于枚举,常量必须是相同的类型!
- Datatype有一个通用参数(或将要有),所以Datatype type = ...。
- @卢卡斯,是的,你说的对,但这不是我的观点。我的观点是:"编译器如何知道parse返回整数?"实际上,它可能是一根长的、一根绳子,等等。编译器没有这些信息。
- 是的,我知道。但是为什么它们必须与Java 1.5所理解的类型相同,并且与EDCOX1(12)所理解的相同的泛型类型相同。为什么?我在这个问题上疯了
- @汤姆:枚举的要点是有一堆相同类型的常量,对吧?根据您的建议,您打破了这一要求,因为枚举的常量将是不同类型的…
- @克丽丝:但也许在编写enumJSR的时候,提到相同的类型就意味着我们以前所理解的类型……你确定你在说什么吗?
- 卢卡斯:我认为Java 1.4或1.5的问题是一样的。您需要一组给定类型的常量,因为您希望对该类型的变量调用方法(不知道它实际包含哪个常量)。要做到这一点,方法签名不能在不同常量之间更改。它不是来自1.4或1.5,它只是来自Java是静态类型的事实。
- @克丽丝:这就是编译器删除通用类型信息的原因。因此,在运行时,我的泛型常量将再次属于同一类型。但编译器知道的更多。这就是编译器可以将类型转换生成字节代码的原因。改变的不是方法签名。生成铸件!
- @卢卡斯:我建议你不要再考虑1.4或1.5的问题了。回到1.4中的例子。你真的能用你的例子做些什么吗?我的意思是,做一些人们通常想做的事情与枚举?编辑:我不是指"1.4中的示例",而是指使用泛型的示例,但不使用enum。明白我的意思吗?试着找一个你打算用它做什么的例子。
- +我反对你的论点。
我就是这么想的-
常规类有实例。您创建了一个类的新实例,出于某种目的使用它,然后释放它。例如,List是字符串列表。我可以用字符串做任何我想做的事情,然后当我完成后,我可以用整数做同样的功能。
对我来说,枚举器不是您创建实例的类型。这和单身汉一样。因此,我可以理解为什么Java不允许泛型为Enums,因为您不能创建一个新类型的枚举实例,就像使用类一样使用临时。枚举应该是静态的,并且全局只有一个实例。对我来说,允许全局只有一个实例的类使用泛型是没有意义的。
我希望这有帮助。
- 枚举的实例与您声明的实例一样多。
- 这对我来说很有意义。我提供了一个很好的例子来说明我为什么要这样做。它与创建类型安全枚举的经典方法一起工作。但不是用enum的方式。静态实例仍然是一个实例。为什么它不应该有一个泛型类型参数?
- @汤姆-你什么意思?如果我创建一个枚举颜色红、绿、白系统中只有一种颜色.white。我已经声明过颜色一次,并且只有一个颜色实例。
- @Amir Raminfar有三个Color的例子:red、green和white。
- 不,有三个Color实例。检查生成的字节代码。他们是public static final Color个实例!
- @卢卡斯,虽然我可能同意你的观点,但我认为这会更令人困惑。静态实例不是实例。例如Java中的数学类。创建math和math是否有意义?我不知道我是否解释得对。但是在C++中泛型被称为模板。因为类实际上是您正在尝试做的事情的模板。但是静态类不是模板。只有一种方法可以做到。我说得有道理吗?
- 对,我不是指三种颜色,我只是指一种红色。不好意思弄混了。
- @阿米尔,你把事情搞混了。Math没有实例!只有静态方法!
- 我猜你说的是你应该能够创建枚举颜色,绿色,我可以看到它是如何有用的。
- @卢卡斯,不,我想我知道静态方法和单例方法是什么。静态方法是数学方法。singleton只是该类的一个实例。我说的是前一个(数学课)你不能创建一个新的颜色实例。就像你不能创建一个新的数学实例一样。对吗?
- @阿米尔。确切地。我想这么做。虽然颜色的类型看起来并不有用,但我认为我的示例是一个更现实的用例。
- @卢卡斯和汤姆-我应该用不同的措辞来回答。对于枚举,一旦声明了类型,就只有这些了。不能在运行时创建新的颜色实例。因为在Java中,泛型实际上只用于编译时间,所以我可以理解为什么它不支持枚举泛型。
- +我赞成你的论点。
- @阿米尔,你也看不到,对吧?真是太邪恶了!
- 谢谢。我真的在想我在做什么!如果在定义了一个新实例之后不能创建它,那么泛型的意义是什么?也许我们可以在JDK7中获得这个特性:)
- @阿米尔。我知道.这就是类型擦除。我们必须面对的完全不同的故事…-)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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行的断言将计算为true。
但是在19号线我们遇到了一个问题。longge和intge是同一个对象(正如第16行的断言所示)。解析("1")将返回什么?运行时通用类型信息不可用。所以在运行时无法确定parse方法的t。
枚举基本上是静态的,它们只存在一次。将泛型类型应用于静态类型是没有意义的。
我希望这有帮助。
注意-这不是工作代码。它使用了原始问题中建议的语法。
- 嗯,我不认为这会起作用:... = GenericEnum.SIMPLE,因为GenericEnum.SIMPLE是一个常数。它是通用类型最终被绑定(即SIMPLE实际上是public static final GenericEnum SIMPLE;并且不能再更改。你可以不安全地投它,如GenericEnum longGE = (GenericEnum) GenericEnum.SIMPLE;,但正如我所说,这是不安全的投…