百科问答小站 logo
百科问答小站 font logo



Java 不能实现真正泛型的原因是什么? 第1页

  

user avatar   glavo 网友的相关建议: 
      

首先并没有什么“真正的泛型”。C++ 的模板是泛型,Java 基于擦除实现的泛型也是泛型。

事实上,对于泛型的翻译有两种策略:同构翻译(homogeneous translation)和异构翻译(heterogeneous translation)。C++ 和(目前的)Java 的策略分别处于异构翻译和同构翻译的极端,一个为每种类型组合都创建一份特化,一个所有类型共享一种实现。而 C#/CLR 处于两者中间,为共享布局的引用类型同构翻译,为值类型异构翻译。

不管是同构翻译还是异构翻译,都有自己的优缺点。Java 的擦除实现带有不小的问题,常常被人诟病,但同样具有巨大的优势(当然如果擦除实现没有这么大优势的话,Java 也不会采用这种实现了)。

类型擦除的问题

对 Java 的诟病,以及对具化泛型的期望,往往在这几点上:

  • 获取泛型参数的具体值。程序员可能会想要知道一个 List 到底是 List<String> 还是 List<Integer>,想要拿到实际泛型参数相关的信息,而因为类型擦除,实际上并不能做到这一点。
  • 布局特化。当前基于擦除的实现要求泛型参数类型必须拥有公共运行时表示(common runtime representation),在 Java 里这意味着只能是一组引用类型,而不能为原始类型。这导致我们只能用 List<Integer>,而用不了针对 int 进行布局特化的 List<int>,底层存放的全是对 Integer 对象的引用,这造成了巨大的内存浪费,同时对现代 CPU 的缓存策略极端不友好,大量间接寻址产生大量 cache miss,产生大量的性能下降。这也是当前 Java 泛型擦除最大的问题。
  • 运行时类型检查。因为泛型实际参数会被擦除,List<String> 会被擦除为 List,所以当通过一些手段(强制转换,raw type 等)将其他类型的值放入这个 List 的时候并不会出错,直到实际访问时才会发生问题。

实际上这不是单一的需求和目标,各自的目的不同,付出的成本与对应的收益也不同,不能从一而论。

模板(异构翻译)的问题

看起来类型擦除有不少的问题,但另一边的模板也存在一些问题。

模板可以把具体操作推迟到被实例化的时候,根据传入的类型参数具体内容而确定表达式的具体含义,并且每个特化有单独的布局,编译器能为每个特化单独优化,这看起来不错。

但是,异构翻译的问题也是显然的。模板的每个实例都要有着不同的代码,这意味着模板展开会导致代码膨胀,产生更大的硬盘和内存占用。像 Scala 提供了 @specialized 注解,可以为原始类型生成特化的版本,结果就是因为指数爆炸,可以很轻松的用几行代码生成上百兆的 JAR。当然这是很极端的例子,一般不可能产生如此之多的模板实例,但 C++ 模板产生的代码膨胀也是必须要关心的问题。

同时,因为 vector<int>vector<float> 是无关的两个类型,C++ 缺乏 Java 的类型通配符或者 C# 的声明处类型变异等能力。事实上,参数化类型组更适合通过同构翻译来表现。

那么,类型擦除的优势有哪些?

前面说了擦除的问题所在,这些一直被诟病的问题让擦除看起来并不是一个好的选择。但事实上,选择类型擦除的方式实现泛型对于 2004 年的 Java 来说是一个明智且务实的选择,即使放到现在来看,它的优点依然是不可忽视的。

1. 兼容性

使得当时的 Java 选择擦除最主要的原因是兼容性

Java 在 2004 年已经积累了大量的生态,如果把现有的类修改成泛型类,需要让所有用户全部重新修改或编译,那完全是不可想象的,会直接导致 Java 1.4 前后生态完全分裂。而 C# 的选择是原类放在那不修改,新创建一套平行的泛型化的库,让用户慢慢抛弃老库。这种选择对于当时用户不多的 C# 来说是可以接受的,但对于已经有大量用户的 Java 来说,这一样是会让生态产生巨大的分裂,并不是一个好的选择。

而 Java 现在的擦除实现做到了一个非常夸张的目标:它完全维护了二进制兼容性和源代码兼容性。

虽然一些类修改为了泛型类,但所有用到它的地方都不受任何影响。用 Java 1.4 或更早版本编译出的字节码一样能用,同时也能直接不做修改的在 Java 1.5 中进行编译。所有用户都可以自由的选择如何迁移到泛型类,用户的用户也不会受到影响。能够维护这种完全的兼容性,Java 的泛型设计在当时来说可谓完全成功。

2. 类型系统的自由度

其次,擦除维护了 JVM 生态类型系统的自由度。

虽然 Java 和 JVM 常常被绑定在一起,但它们是各自独立的,有着各自的规范。根据一些统计,有超过 200 种语言会编译到字节码。其中一些语言和 Java 的类型系统并不完全兼容,渴求更高的表达能力。

类型擦除是一个很好的实现复杂类型的方式,Haskell 就是一个使用类型擦除的典型例子,它大多数类型检查都放在编译时,而在编译后擦除类型。

由于通过类型擦除实现泛型,像 Scala 这样的语言可以以与 Java 泛型高度协同的方案实现远远超出 Java 类型系统表达能力的类型系统,同时保持高度的互操作性。

一个典型的反例就是 C#/CLR。CLR 的泛型系统严重制约了其上语言泛型的表达能力,C#、F# 等语言都因此难以拥有更强的类型系统,任何与 CLR 冲突的小区别都是致命的(譬如,因为 CLR 缺乏 bottom type,Scala 的 ListOption 等实现在其上就几乎无法直接表达,需要重写改写以适应 CLR),必须和 CLR 共同演化才能实现很多能力。在 CLR 上擦除泛型以实现更强大的类型系统是可能的,但代价就是完全和 CLR 原生的泛型生态撕裂,而原生就基于擦除实现的泛型就不会有这个问题。

3. 运行时开销

前面已经说过异构翻译导致的代码膨胀问题,这会产生难以避免的运行时开销。当然,这个开销往往是值得的,但异构翻译的开销不止于此,另一个重要的开销问题就是运行时类型检查。

运行时类型检查可以避免堆污染,维护安全性,并对于 List<String> 这样的简单类型来说,看起来开销往往是微不足道的。但是,在遇到复杂的类型(比如说 Map<? extends List<? super Foo>>, ? super Set<? extends Bar>>)时 ,类型检查的开销可能会出乎意料的大,这也是不得不考虑的问题。

未来 —— 通往 Valhalla 的道路

异构翻译和同构翻译都存在自己的弊病,而 OpenJDK 的 Project Valhalla 吸取了探索者们的经验与教训,在 Java 语言和 JVM 中融合两种方案尝试中接近了终点。

原始类型方面,Valhalla 已经准备好了很漂亮的答卷(JEP 401JEP 402)。而在泛型方面,Project Valhalla 有着非常具有野心的目标:它专注于实现布局特化,弥补 Java 完全擦除泛型最主要的性能问题,绕开运行时类型检查等高开销操作,同时还要保留逐步迁移的兼容性,还要保持擦除带来的类型系统高自由度的优势。

我翻译了一篇 Valhalla 项目的设计说明,从中可以更深入的了解 Valhalla 的目标、历史,以及 L-World 下全新的类型系统。

Project Valhalla 在权衡下只关注于布局特化,对其他具化泛型的用途并没有太多帮助。不过具化泛型的其他功能仅在语言层面就可以很好的实现。譬如 Scala 用 ClassTagTypeTag 就能简单地做到保留类型的功能:

       def newArray[T : ClassTag](n: Int) = new Array[T](n) newArray[String](10) // ok  class Foo[T : ClassTag] {   def newArray(n: Int): Array[T] = new Array(n) }  new Foo[String]().newArray(10) // ok  def typeOf[T : TypeTag] = implicitly[TypeTag[T]].tpe println(typeOf[Map[_, Seq[_ <: String]]]) // Map[_, Seq[_ <: String]]     

相对来说,这些功能的代价较为高昂,受益却略偏低,Valhalla 不关心于此也是能接受的。

参考:

valhalla-docs-zh: in-defense-of-erasure.md

valhalla-docs-zh: 01-background.md




  

相关话题

  为什么g++能够优化到动态库里的STL? 
  Mac系统下有什么适合图形学的C++IDE?Clion还是VS? 
  Qt出最近版本了,支持全平台开发,而我以前没接触过,一直java,现在如何选择呢? 
  C++ 中,std::atomic 是真正的「原子」吗? 
  如何快速打好Java基础? 
  C++里为何要定义类似size_type,difference_type,iterator这种类型? 
  如何格式化代码能够将类成员/函数的名字对齐? 
  同时会C#、Qt还有Python是一种什么体验? 
  C 与 C++ 谁的效率高,为什么? 
  c++的单例模式为什么不直接全部使用static,而是非要实例化一个对象? 

前一个讨论
为什么会有人坚决不买安卓手机,去花更多的钱买 iPhone ?
下一个讨论
假如新冠疫苗研制成功,而病毒发生了变异,那么疫苗还有效吗?





© 2024-11-21 - tinynew.org. All Rights Reserved.
© 2024-11-21 - tinynew.org. 保留所有权利