Java枚举非常棒。仿制药也是。当然,由于类型擦除,我们都知道后者的局限性。但有一件事我不明白,为什么我不能创建这样的枚举:
1 2 3 4 5
| public enum MyEnum<T> {
LITERAL1<String>,
LITERAL2<Integer>,
LITERAL3<Object>;
} |
然后,这个通用类型参数可以在不同的地方使用。设想一个方法的泛型类型参数:
1
| public <T> T getValue(MyEnum<T> param); |
或者甚至在枚举类本身中:
更具体的例子1
因为上面的例子对某些人来说可能太抽象了,下面是一个更现实的例子,说明我为什么要这样做。在这个例子中,我想使用
- 枚举,因为这样我可以枚举一组有限的属性键
- 泛型,因为这样我就可以有方法级的类型安全性来存储属性
1 2 3 4
| public interface MyProperties {
public <T> void put(MyEnum<T> key, T value);
public <T> T get(MyEnum<T> key);
} |
更具体的例子2
我列举了数据类型:
1 2 3 4 5 6 7 8 9 10 11
| public interface DataType<T> {}
public enum SQLDataType<T> implements DataType<T> {
TINYINT<Byte>,
SMALLINT<Short>,
INT<Integer>,
BIGINT<Long>,
CLOB<String>,
VARCHAR<String>,
...
} |
显然,每个枚举文字都将具有基于泛型类型的附加属性,同时也是一个枚举(不可变、单例、可枚举等)。
问题:
没人想到这个吗?这是与编译器相关的限制吗?考虑到这个事实,关键字"enum"是作为语法糖实现的,它向JVM表示生成的代码,我不理解这一限制。
谁能向我解释这个?在回答之前,请考虑:
- 我知道通用类型被删除了:—)
- 我知道有使用类对象的解决方法。他们是解决办法。
- 如果适用,泛型类型将导致编译器生成的类型强制转换(例如,在调用convert()方法时)
- 泛型类型将位于枚举上。因此,它受枚举的每个文本的约束。因此,编译器会知道在编写类似于String string = LITERAL1.convert(myObject); Integer integer = LITERAL2.convert(myObject);的内容时应用哪种类型。
- 这同样适用于T getvalue()方法中的泛型类型参数。编译器可以在调用String string = someClass.getValue(LITERAL1)时应用类型转换。
- 我也不理解这个限制。我最近遇到了这样一个问题,我的枚举包含不同的"可比较"类型,并且对于泛型,只有相同类型的可比较类型可以进行比较,而不需要禁止任何警告(即使在运行时会比较正确的类型)。我可以通过使用枚举中绑定的类型来指定支持哪种可比较的类型来消除这些警告,但我必须添加SuppressWarnings注释-不能绕过它!因为CompareTo无论如何都会抛出类强制转换异常,我想这没关系,但是…
- 我投票结束了自己的问题,认为这"不具有建设性"。这个问题很可能在回答之前就开始抱怨JCP关于语言设计的决定或者我自己的意图(例如,对于那些能看到已删除答案的人来说,最初接受但已删除的答案)。
- (+1)我正试图缩小我的项目中的一个类型安全差距,被这个任意的限制所阻止。试想一下:将EDCOX1的0个词变成Java 1.5之前使用的"类型化EnUM"习惯用语。突然间,可以参数化枚举成员。这可能就是我现在要做的。
- @Markotopolinik:这是你能做的一件事,但是你会失去所有那些融入到语言和JDK中的出色的枚举特性。例如,switch支持,或EnumSet和EnumMap支持。
- 我知道,这很糟糕。)事实上,我不得不把它放在我想要应用它的两个枚举中的一个。
- 我有一个很难想象的场景,在这个场景中,这可能是有用的。你有更具体的设想吗?
- @Edwindalorzo:用JooQ的一个具体例子更新了这个问题,在过去,这个例子会非常有用。
- @卢卡塞德,我明白你的意思了。看起来很酷一个新功能。也许你应该在投币项目的邮件列表中提出这个建议。我在Enums上看到了其他有趣的建议,但不像你的建议。
- 完全同意。没有泛型的枚举已损坏。你的案子也是我的。如果我需要泛型枚举,我放弃JDK5,用普通的Java 1.4风格来实现它。这种方法还有额外的好处:我不必强制在一个类甚至包中包含所有常量。因此,按特性样式进行打包的效果更好。这对于像"枚举"这样的配置来说是完美的——常量根据它们的逻辑含义分布在包中(如果我希望看到它们,我会显示类型层次结构)。
- @Edwindalorzo:这个列表还有效吗?不管怎样,我过去没有在这些名单上做过什么了不起的经历…
- 实际上,这是一个很好的问题。遗憾的是,几乎所有的答案都没有抓住要点,或者完全忽略了这个问题。不过,在某种程度上,我认为这是可以预料的,因为似乎没有明显的答案。
- 这是一个好主意,但它确实增加了JLS的额外功能,使维护和新功能更加困难,因此所有的阻力。Java已经因为向后兼容性原因而被黑客攻击,保持它的纯净是我现在认为的1优先级。所以这是一个公平的论点,如果你需要在你的枚举中有这样的复杂度,也许你应该用OO来建模。
- 你所说的"保持纯洁"是什么意思?另外,一般枚举a)复杂和b)非oo如何?此外,您检查过openjdk.java.net/jeps/301吗?
目前正在讨论从jep-301增强型Enums开始。Jep中给出的例子,正是我所寻找的:
1 2 3 4 5 6 7 8 9 10 11 12
| 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
- 截至2018年12月,Jep301再次出现了一些生命迹象,但略读这一讨论就可以清楚地看到,这些问题还远未得到解决。
答案是:
because of type erasure
这两种方法都不可能,因为参数类型被擦除。
1 2
| public <T > T getValue (MyEnum <T > param );
public T convert (Object); |
要实现这些方法,您可以将枚举构造为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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);。我猜这个方法可以把一堆不同的类型缩小到,例如字符串。字符串对象的构造是运行时的-编译器什么也不做。因此,您需要知道运行时类型,例如string.class。还是我错过了什么?
- 我认为您遗漏了一个事实,即泛型类型位于枚举(或其生成的类)上。枚举的唯一实例是它的文本,这些文本都提供常量泛型类型绑定。因此公共t转换(object)中t没有歧义;
- 啊哈!知道了。你比我聪明:)
- 我想这归根结底是一个枚举的类,它的计算方法与其他所有方法类似。在Java中,尽管事物看起来是常数,但它们不是。考虑EDCOX1的3和静态块static { FOO ="bar"; }-即使在编译时已知常数也会被评估。
- "这两种方法都不可能"-->所以它们是否可能?
枚举中还有其他方法不起作用。MyEnum.values()会返回什么?
那MyEnum.valueOf(String name)呢?
如果您认为编译器可以使类泛型方法
public static MyEnum valueOf(String name);
为了像MyEnum myStringEnum = MyEnum.value("some string property")那样称呼它,这也行不通。例如,如果你称之为MyEnum myIntEnum = MyEnum.value("some string property")呢?实现该方法是不可能正确工作的,例如,由于类型擦除,当您像MyEnum.value("some double property")那样调用它时,抛出异常或返回空值。
- 他们为什么不工作?他们只使用通配符……:MyEnum>[] values()和MyEnum> valueOf(...)
- 但是您不能像这样进行赋值,因为类型不兼容。在这种情况下,您还希望得到空值,因为您希望返回不存在于Double属性名的MyEnum,并且由于擦除,您无法使该方法正常工作。
- 另外,如果循环遍历values(),则需要使用myenum<?>这不是您通常想要的,因为例如,您不能只通过int属性进行循环。另外,您还需要做很多您想要避免的强制转换,我建议为每种类型创建不同的枚举,或者用实例创建您自己的类…
- 嗯,我想你不能两者兼得。通常,我使用自己的"枚举"实现。只是Enum有很多其他有用的特性,这个特性是可选的,也是非常有用的…
因为你不能。说真的。这可以添加到语言规范中,但还没有。这会增加一些复杂性。成本效益意味着它不是一个高优先级。
更新:目前正在添加到jep 301下的语言:增强的枚举。
- 你能详细说明一下并发症吗?
- 我已经考虑这几天了,我仍然没有看到并发症。
坦率地说,这似乎更像是寻找问题的解决方案。
JavaEnUM的全部目的是对以相似的属性共享相似类型属性的类型实例的枚举进行建模,该方法提供了可比字符串或整数表示的一致性和丰富性。
以文本书枚举为例。这不是非常有用或一致的:
1 2 3 4 5 6
| public enum Planet<T>{
Earth<Planet>,
Venus<String>,
Mars<Long>
...etc.
} |
为什么我希望我的不同行星有不同的通用类型转换?它能解决什么问题?它是否有理由使语言语义复杂化?如果我真的需要这个行为是一个枚举实现它的最佳工具?
另外,您如何管理复杂的转换?
例如
1 2 3 4
| public enum BadIdea<T>{
INSTANCE1<Long>,
INSTANCE2<MyComplexClass>;
} |
使用StringInteger很容易提供名称或顺序。但是泛型允许您提供任何类型的。您将如何管理到MyComplexClass的转换?现在,通过强制编译器知道可以提供给泛型枚举的类型的有限子集,以及对概念(泛型)引入额外的混淆(泛型),将两个构造搞得一团糟,这似乎已经被许多程序员所回避。
- 为什么?查看我更新的答案,以查看更真实的示例…
- 它比任何东西都更具有哲学意义。枚举实例并不代表各种泛型类型的"属性"。实例是枚举类型的实例。您建议的"特性"违背了构造的意图,即解决与某个特定的有点可疑的应用程序相关的问题。
- 想到几个例子,如果它不有用,这是一个可怕的论点,它将永远不会有用。
- 这些例子支持这一点。枚举实例是该类型的子类(nice和simple),而包含泛型的枚举实例则是一个非常复杂的领域,具有非常模糊的好处。如果你要投我的反对票,那么你也应该投汤姆·霍丁的反对票,因为他用不多的话说了同样的话。
- 请记住,枚举也是创建单例(或n-to n)实例的一种方法。假设您有一个可转换类型的列表…StringifierEnum。你想把它概括为NUMBER.convert(Number)、CURRENCY.convert(Double)、TO_STRING.convert(Object)、LYRICS.convert(String[])…为什么他们都必须把Object作为参数类型?为什么不把它与StringifierEnum和public abstract String convert ( T object )一起遗传呢?在这里无法使用泛型可以防止在编译时检测到容易检测到的错误,并将它们移动到运行时。
- 我想我对此的感觉如下。是的,创建singleton是enum的常见应用程序,但您所描述的不是enum。你在描述一些不同的东西。在枚举中添加泛型只是混淆了enum的概念,以满足使用enum的实际考虑。我认为如果你想要这个能力,它应该通过其他的构造来实现。
- @马尔科托普尼克,我不认为这里的严重性有问题。语言设计决策已经做出,我只是没有听到一个令人信服的理由来改变它们。粗略的一瞥意味着,在这种特定的情况下,它将允许OP使用enum实现其解决方案,但它是否解决了基本的语言限制?我的直觉告诉我不行。枚举很简单,可以解决一个简单的问题。他们很好,应该单独呆着。
- NSfYN55枚举是Java中最复杂、最神奇的语言特征之一。例如,没有一个其他语言功能可以自动生成静态方法。另外,每个枚举类型都已经是泛型类型的实例。
- @马尔科托普尼克情结,神奇?他们之所以被称为enum,是有原因的。他们的工作是枚举属于某个类的实例的有限列表。人们以聪明的方式重新调整了它们的用途,但这并不意味着特性的设计动力已经改变。
- @NSFyn55的设计目标是使枚举成员变得强大和灵活,这就是为什么它们支持诸如自定义实例方法甚至可变实例变量等高级功能的原因。它们被设计为对每个成员单独进行专门化的行为,这样它们就可以作为活动的合作者参与复杂的使用场景。最重要的是,JavaEnm是为了使通用枚举成语类型安全而设计的,但是缺少类型参数使它无法达到最珍视的目标。我非常确信有一个非常具体的原因。
- @马尔科托普尼克有一个特定的原因,它已经被提到无数次。Type Erasure。您的泛型Option或Property类的实例在运行时会丢失其类型标识。
- @nsfyn55所有提到类型擦除的响应都缺少要点:这是关于编译时类型安全的。我已经发布了一个具体的例子,为了您的方便,我把它放在这里:class Emun> { } abstract class Xenu extends Emun> { abstract T convert(Object o); static Xenu STRING = new Xenu() { String convert(Object o) { return o.toString(); } }; String s = STRING.convert(3); }btw您知道所有具有真正的类型系统的语言,如haskell,也都有类型删除吗?
- @马尔科托普尼克没有冒犯,但对某些人来说,挂在类型安全的美丽,这是一个相当不礼貌的做法。接受Object类型的东西并应用盲转换?它可以与String一起使用,但不那么做作的例子呢?EDCOX1〔19〕为了正确地执行Java,必须支持逆变。
- @NSFYN55现在您正在下降到细节级别,一个示例内联到一个注释(a)中不可能覆盖,并且(b)不需要这样做,因为它的目的是证明一个而且只有一个断言。如果你提到OP的想法,他是JooQ库的作者,该库通常需要使用一组定义良好的封闭类型。我的用例是完全不同的,但是我再次使用一组封闭的、定义良好的类型。
- 我没注意到你提到的矛盾…Java确实支持它,只是它使用了站点,这使得它有点笨拙。interface Converter { OUT convert(IN in); } Set convertListToSet(List in, Converter super List, ? extends Set> converter) { return converter.convert(in); }我们必须手工计算出每次消费的类型和生产的类型,并相应地指定界限。
- 当然有用。这次行动给出了一个完美的例子——还有更多。每个枚举值都需要一个或多个特定类型作为其行为的参数/专门化,例如EARTH, MARS。
- @航海家没有一种语言是完整的。在我看来,"有用"不是正确的鉴别器。我在找的是"必要的",你必须在沙地的某个地方划一条线。这一行是语言合理支持其预期用例的决定。你和OP的提议没有什么错。它只是坐错了方向。
- @NSYFN55我希望这个功能已经存在,甚至在我的IDE中打印出来,在我的应用程序中使用。枚举构造函数是唯一不接受类型参数的构造函数,允许它只会使枚举与语言的其余部分保持一致,并降低实现相同任务的代码复杂性。所以这对我来说是好的,在这条线的右边。事实上,我对语言特性的标准是最小化代码复杂性。在非常复杂的应用程序中会产生很大的差异
因为"enum"是枚举的缩写。它只是一组命名常量,用来代替序数,使代码更易于阅读。
我不知道类型参数化常量的预期含义是什么。
- 在java.lang.String中:public static final Comparator CASE_INSENSITIVE_ORDER中。你现在明白了吗?-)
- 不,这是一个枚举吗?它更像是一个函数指针。
- 你说你看不到类型参数化常量的含义。所以我给你看了一个类型参数化常量(不是函数指针)。
- 当然不是,因为Java语言不知道函数指针这样的东西。尽管如此,常量不是类型参数化的,但它是类型。类型参数本身是常量,而不是类型变量。
- 但是枚举语法只是语法上的糖分。在下面,它们和CASE_INSENSITIVE_ORDER一模一样…如果Comparator是一个枚举,为什么不将与相关联的文本绑定?
- 如果comparator是一个枚举,那么实际上我们可以拥有各种奇怪的东西。可能类似于integer。但事实并非如此。
- 查看更新后的问题。我添加了一个更真实的示例来说明我的用例
我想是因为基本上不能引用枚举
如果JVM允许,您将在哪里设置T类?
枚举是应该总是相同的数据,或者至少是不会发生一般性变化的数据。
新肌瘤< >
以下方法可能仍然有用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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()方法。我考虑枚举常量,对我来说,它意味着不可变。所以即使可能,我也不会这么做。
- 枚举在编译器生成的代码中显示。每个文本实际上是通过调用私有构造函数创建的。因此,有机会在编译时将泛型类型传递给构造函数。
- 枚举上的setter完全正常。特别是如果您使用EnUM来创建一个单独的项目(Joshua Bloch的项目3有效Java)