繁体   English   中英

为什么不从下一个 JVM 中删除类型擦除?

[英]Why not remove type erasure from the next JVM?

Java 在 Java 5 中引入了带有泛型的类型擦除,因此它们可以在旧版本的 Java 上工作。 这是兼容性的权衡。 我们已经失去了兼容性[1] [2] [3]——字节码可以在更高版本的 JVM 上运行,但不能在早期版本上运行。 这看起来是更糟糕的选择:我们已经丢失了类型信息,我们仍然无法在旧版本上运行为新版本 JVM 编译的字节码。 发生了什么?

具体来说,我是问是否有任何技术原因导致无法在下一版本的 JVM 中删除类型擦除(假设,像以前的版本一样,它的字节码无论如何都无法在上一版本上运行)。

[3]:对于那些真正喜欢它的人来说,类型擦除可以以类似于retrolambda的方式向后移植。

编辑:我认为对向后与向前兼容性定义的讨论掩盖了这个问题。

类型擦除不仅仅是您可以打开或关闭的字节码功能。

它影响整个运行时环境的工作方式。 如果您希望能够查询泛型类的每个实例的泛型类型,这意味着为泛型类的每个对象实例化创建与运行时Class表示相当的元信息。

如果你写new ArrayList<String>(); new ArrayList<Number>(); new ArrayList<Object>() new ArrayList<String>(); new ArrayList<Number>(); new ArrayList<Object>() new ArrayList<String>(); new ArrayList<Number>(); new ArrayList<Object>()您不仅创建了三个对象,还可能创建了三个反映类型的额外元对象, ArrayList<String>ArrayList<Number>ArrayList<Object> ,如果它们之前不存在的话.

考虑到在典型应用程序中使用了数千种不同的List签名,其中大多数从未在需要此类反射的地方使用过(由于缺少此功能,我们可以得出结论,目前,所有这些签名没有这样的反射工作)。

当然,这乘以千种不同的泛型列表类型意味着千种不同的泛型迭代器类型、千种拆分器和流化身,甚至不包括实现的内部类。

它甚至会影响没有对象分配的地方,这些地方当前正在探索引擎盖下的类型擦除,例如Collections.emptyList()Function.identity()Comparator.naturalOrder()等。每次调用它们时都返回相同的实例. 如果您坚持让特定捕获的泛型类型可以反射检查,这将不再起作用。 所以如果你写

List<String> list=Collections.emptyList();
List<Number> list=Collections.emptyList();

您将不得不接收两个不同的实例,每个实例在getClass()或未来的等效项上报告不同。


看来,希望获得这种能力的人对他们的特定方法有一种狭隘的看法,如果他们能够反思地发现一个特定参数实际上是二种或三种类型中的一种,那就太好了,但从不考虑携带的重量有关数千个泛型类的潜在成百上千个泛型实例的元信息。

这就是我们必须询问我们得到什么回报的地方:支持有问题的编码风格的能力(这是由于通过反射找到的信息而改变代码行为的全部内容)。


到目前为止,答案仅解决了删除类型擦除的简单方面,以及内省实际实例类型的愿望。 实际实例具有可以报告的具体类型。 正如提到的此评论用户the8472 ,用于去除类型擦除的需求往往也意味着希望用于能够铸造到(T)或创建经由阵列new T[]或接入经由一个类型变量的类型T.class

这将引发真正的噩梦。 类型变量是与具体实例的实际类型不同的野兽。 类型变量可以解析为a,例如? extends Comparator<? super Number> ? extends Comparator<? super Number> ? extends Comparator<? super Number>举一个(相当简单的)例子。 提供必要的元信息意味着不仅对象分配变得更加昂贵,每个方法调用都可能增加这些额外的成本,甚至更大的扩展,因为我们现在不仅在谈论泛型类与实际类的组合,而且还有所有可能的通配符组合,即使是嵌套的泛型类型。

请记住,类型参数的实际类型也可以引用其他类型参数,从而将类型检查变成一个非常复杂的过程,如果允许从它,每个存储操作都必须重复它。

除了沉重的性能问题外,复杂性还引发了另一个问题。 如果你查看javac的 bug 跟踪列表或 Stackoverflow 的相关问题,你可能会注意到过程不仅复杂,而且容易出错。 目前, javac每个次要版本都包含有关泛型类型签名匹配的更改和修复,影响将被接受或拒绝的内容。 我很确定,您不希望像类型转换、变量赋值或数组存储这样的内在 JVM 操作成为这种复杂性的受害者,对每个版本中什么是合法的或不合法的有不同的想法,或者突然拒绝javac接受的内容由于规则不匹配而导致编译时。

在某种程度上, valhalla 项目将来会删除擦除,以启用值类型的专门实现。

或者更准确地说,类型擦除实际上意味着没有泛型的类型特化,而 valhalla 将引入对原语的特化。

具体来说,我问是否有任何技术原因导致无法在下一版本的 JVM 中删除类型擦除

性能。 您不必为泛型类型、实例或生成的类的所有组合生成专门的代码不必携带类型标记、多态内联缓存和运行时类型检查(编译器生成的instanceof检查)保持简单,我们仍然可以得到最多通过编译时检查的类型安全。

当然也有很多缺点,但已经做出了权衡,问题是什么会激励 JVM 开发人员改变这种权衡。

这也可能是一个兼容性问题,可能有代码执行未经检查的强制转换以通过依赖类型擦除来滥用泛型集合,如果强制执行类型约束就会中断。

您对向后兼容性的理解是错误的。

期望的目标是JVM 能够正确运行库代码,即使使用代码也不会改变。 这允许用户将他们的 Java 版本可靠地升级到比编写代码时更新的版本。

具体来说,我是问是否有任何技术原因导致无法在下一版本的 JVM 中删除类型擦除(假设,像以前的版本一样,它的字节码无论如何都无法在上一版本上运行)。

因为它可能会破坏向后兼容性。 (真正的向后兼容性......不是你最初的问题所谈论的那种神话般的。)

是的,这是一个技术原因,因为向后兼容性是 Java 语言/平台最重要的特性之一。 至少,这是甲骨文和甲骨文付费客户的观点。

如果类型擦除停止工作,那么在过去 20 年中,许多企业在基于 Java 的软件上的数千亿美元投资突然面临风险。


对于那些真正喜欢它的人来说,类型擦除可以以类似于 retrolambda 的方式向后移植。

也许。 但需要有人最终证明这是可行的。 即使它被证明是可行的,改造、重新编写代码、重新测试等的成本也将是巨大的。


问问自己这个。 为什么 Sun/Oracle 没有删除Thread.stop()

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM