为什么不从下一个 JVM 中删除类型擦除?
Why not remove type erasure from the next JVM?
Java 在 Java 5 中引入了带有泛型的类型擦除,因此它们可以在旧版本的 Java 上工作。这是兼容性的折衷。我们已经失去了兼容性[1] [2] [3]——字节码在更高版本的 JVM 上可以是 运行 而不是更早的版本。这看起来可能是更糟糕的选择:我们已经丢失了类型信息,而且我们仍然无法 运行 为旧版本的 JVM 的新版本编译字节码。发生什么事了?
具体来说,我想问的是是否有任何技术原因导致无法在下一版本的 JVM 中删除类型擦除(假设与以前的版本一样,其字节码将无法 运行 最后一个版本)。
[3]:对于那些真正喜欢它的人来说,类型擦除可以以类似于 retrolambda 的方式向后移植。
编辑:我认为关于向后兼容性与向前兼容性定义的讨论掩盖了这个问题。
你对向后兼容性的理解是错误的。
期望的目标是 new JVM 能够 运行 old library code correctly and changed even with 新代码。这允许用户可靠地将他们的 Java 版本升级到比代码编写的版本更新得多的版本。
将来会在某种程度上删除擦除 project valhalla 以启用值类型的专门实现。
或者更准确地说,类型擦除实际上意味着没有泛型的类型特化,而 valhalla 将引入基元的特化。
Specifically I'm asking if there are any technical reasons why type erasure couldn't be removed in the next version of the JVM
性能。您不必为泛型类型、实例或生成的所有组合生成专用代码 类 不必携带类型标签、多态内联缓存和运行时类型检查(编译器生成的 instanceof
检查) 保持简单,我们仍然通过编译时检查获得大部分类型安全。
当然也有很多缺点,但权衡已经做出,问题是什么会激励 JVM 开发人员改变这种权衡。
而且它也可能是兼容性问题,可能有代码执行未经检查的强制转换,通过依赖类型擦除来滥用泛型集合,如果强制执行类型约束,类型擦除会中断。
类型擦除不仅仅是您可以打开或关闭的字节码功能。
它影响整个运行时环境的工作方式。如果您希望能够查询泛型 class 的每个实例的泛型类型,则意味着为泛型 Class
的每个对象实例化创建与运行时 Class
表示相当的元信息=59=].
如果您编写 new ArrayList<String>(); new ArrayList<Number>(); new ArrayList<Object>()
,您不仅会创建三个对象,而且可能会创建另外三个反映类型的元对象,ArrayList<String>
、ArrayList<Number>
和 ArrayList<Object>
, 如果它们之前不存在。
考虑到在一个典型的应用程序中使用了数千种不同的 List
签名,其中大多数从未在需要这种反射可用性的地方使用过(由于没有此功能,我们可以得出结论,目前,所有这些都在没有这种反射的情况下工作。
这当然会相乘,千种不同的通用列表类型意味着千种不同的通用迭代器类型,千种拆分器和 Stream 化身,甚至不包括实现的内部 classes。
它甚至会影响没有对象分配的地方,这些地方目前正在底层利用类型擦除,例如Collections.emptyList()
、Function.identity()
或 Comparator.naturalOrder()
等。return 每次调用相同的实例。如果你坚持让特定捕获的泛型类型反射检查,这将不再有效。所以如果你写
List<String> list=Collections.emptyList();
List<Number> list=Collections.emptyList();
您将不得不收到两个不同的实例,每个实例都在 getClass()
或未来的等效值上报告不同的情况。
看来,希望获得这种能力的人对他们的特定方法的看法是狭隘的,如果他们能够反思地找出一个特定的参数是否实际上是两种或三种类型中的一种,那就太好了,但从没想过关于携带关于可能成百上千个通用实例化的元信息的重量 classes.
这是我们不得不问我们在 return 中获得了什么的地方:支持有问题的编码风格的能力(这就是由于通过反射找到的信息而改变代码行为的全部内容) .
到目前为止,答案仅解决了删除类型擦除的 easy 方面,希望内省实际实例的类型。实际实例具有具体类型,可以报告。正如 from the user the8472 中提到的,删除类型擦除的需求通常也意味着希望能够转换为 (T)
或通过 new T[]
创建数组或访问类型变量的类型通过 T.class
.
这会引发真正的噩梦。类型变量与具体实例的实际类型不同。类型变量可以解析为 a,例如? extends Comparator<? super Number>
举一个(相当简单的)例子。提供必要的元信息意味着不仅对象分配变得更加昂贵,每个单独的方法调用都可能带来这些额外的成本,甚至更大的扩展,因为我们现在不仅在谈论泛型 classes 的组合具有实际的 classes,还有所有可能的通配符组合,甚至是嵌套的泛型类型。
请记住,类型参数的实际类型也可以引用其他类型参数,将类型检查变成一个非常复杂的过程,如果允许的话,您不仅必须对每个类型转换重复一次从中创建一个数组,每个存储操作都必须重复它。
除了严重的性能问题外,复杂性还引发了另一个问题。如果你查看 javac
的错误跟踪列表或 Whosebug 的相关问题,你可能会注意到该过程不仅复杂,而且容易出错。目前,javac
的每个次要版本都包含有关泛型类型签名匹配的更改和修复,影响将被接受或拒绝的内容。我很确定,您不希望内部 JVM 操作(如类型转换、变量赋值或数组存储)成为这种复杂性的受害者,对每个版本中合法与否有不同的想法,或者突然拒绝 javac
由于规则不匹配,在编译时被接受。
Java 在 Java 5 中引入了带有泛型的类型擦除,因此它们可以在旧版本的 Java 上工作。这是兼容性的折衷。我们已经失去了兼容性[1] [2] [3]——字节码在更高版本的 JVM 上可以是 运行 而不是更早的版本。这看起来可能是更糟糕的选择:我们已经丢失了类型信息,而且我们仍然无法 运行 为旧版本的 JVM 的新版本编译字节码。发生什么事了?
具体来说,我想问的是是否有任何技术原因导致无法在下一版本的 JVM 中删除类型擦除(假设与以前的版本一样,其字节码将无法 运行 最后一个版本)。
[3]:对于那些真正喜欢它的人来说,类型擦除可以以类似于 retrolambda 的方式向后移植。
编辑:我认为关于向后兼容性与向前兼容性定义的讨论掩盖了这个问题。
你对向后兼容性的理解是错误的。
期望的目标是 new JVM 能够 运行 old library code correctly and changed even with 新代码。这允许用户可靠地将他们的 Java 版本升级到比代码编写的版本更新得多的版本。
将来会在某种程度上删除擦除 project valhalla 以启用值类型的专门实现。
或者更准确地说,类型擦除实际上意味着没有泛型的类型特化,而 valhalla 将引入基元的特化。
Specifically I'm asking if there are any technical reasons why type erasure couldn't be removed in the next version of the JVM
性能。您不必为泛型类型、实例或生成的所有组合生成专用代码 类 不必携带类型标签、多态内联缓存和运行时类型检查(编译器生成的 instanceof
检查) 保持简单,我们仍然通过编译时检查获得大部分类型安全。
当然也有很多缺点,但权衡已经做出,问题是什么会激励 JVM 开发人员改变这种权衡。
而且它也可能是兼容性问题,可能有代码执行未经检查的强制转换,通过依赖类型擦除来滥用泛型集合,如果强制执行类型约束,类型擦除会中断。
类型擦除不仅仅是您可以打开或关闭的字节码功能。
它影响整个运行时环境的工作方式。如果您希望能够查询泛型 class 的每个实例的泛型类型,则意味着为泛型 Class
的每个对象实例化创建与运行时 Class
表示相当的元信息=59=].
如果您编写 new ArrayList<String>(); new ArrayList<Number>(); new ArrayList<Object>()
,您不仅会创建三个对象,而且可能会创建另外三个反映类型的元对象,ArrayList<String>
、ArrayList<Number>
和 ArrayList<Object>
, 如果它们之前不存在。
考虑到在一个典型的应用程序中使用了数千种不同的 List
签名,其中大多数从未在需要这种反射可用性的地方使用过(由于没有此功能,我们可以得出结论,目前,所有这些都在没有这种反射的情况下工作。
这当然会相乘,千种不同的通用列表类型意味着千种不同的通用迭代器类型,千种拆分器和 Stream 化身,甚至不包括实现的内部 classes。
它甚至会影响没有对象分配的地方,这些地方目前正在底层利用类型擦除,例如Collections.emptyList()
、Function.identity()
或 Comparator.naturalOrder()
等。return 每次调用相同的实例。如果你坚持让特定捕获的泛型类型反射检查,这将不再有效。所以如果你写
List<String> list=Collections.emptyList();
List<Number> list=Collections.emptyList();
您将不得不收到两个不同的实例,每个实例都在 getClass()
或未来的等效值上报告不同的情况。
看来,希望获得这种能力的人对他们的特定方法的看法是狭隘的,如果他们能够反思地找出一个特定的参数是否实际上是两种或三种类型中的一种,那就太好了,但从没想过关于携带关于可能成百上千个通用实例化的元信息的重量 classes.
这是我们不得不问我们在 return 中获得了什么的地方:支持有问题的编码风格的能力(这就是由于通过反射找到的信息而改变代码行为的全部内容) .
到目前为止,答案仅解决了删除类型擦除的 easy 方面,希望内省实际实例的类型。实际实例具有具体类型,可以报告。正如 (T)
或通过 new T[]
创建数组或访问类型变量的类型通过 T.class
.
这会引发真正的噩梦。类型变量与具体实例的实际类型不同。类型变量可以解析为 a,例如? extends Comparator<? super Number>
举一个(相当简单的)例子。提供必要的元信息意味着不仅对象分配变得更加昂贵,每个单独的方法调用都可能带来这些额外的成本,甚至更大的扩展,因为我们现在不仅在谈论泛型 classes 的组合具有实际的 classes,还有所有可能的通配符组合,甚至是嵌套的泛型类型。
请记住,类型参数的实际类型也可以引用其他类型参数,将类型检查变成一个非常复杂的过程,如果允许的话,您不仅必须对每个类型转换重复一次从中创建一个数组,每个存储操作都必须重复它。
除了严重的性能问题外,复杂性还引发了另一个问题。如果你查看 javac
的错误跟踪列表或 Whosebug 的相关问题,你可能会注意到该过程不仅复杂,而且容易出错。目前,javac
的每个次要版本都包含有关泛型类型签名匹配的更改和修复,影响将被接受或拒绝的内容。我很确定,您不希望内部 JVM 操作(如类型转换、变量赋值或数组存储)成为这种复杂性的受害者,对每个版本中合法与否有不同的想法,或者突然拒绝 javac
由于规则不匹配,在编译时被接受。