在java里,引用类型的数组类型是支持协变的,即 String[]
类型是 Object[]
的子类型,martin问过gosling,回答是希望有一个通用处理数组的简单方法。
void sort(Object[] a, Comparator cmp) { … }
数组的协变被用来确保任意参数类型的数组都可以传入排序方法。随着java引入了泛型,sort方法可以用类型参数,因此数组的协变不再有用。只是考虑到兼容性。
scala里不支持数组的协变,以尝试保持比java更高的纯粹性。即 Array[String]
的实例对象不能赋给Array[Any]
类型的变量。
在数组的设计上,scala是走过弯路的,这里有篇文章翻自martin写的关于scala2.8数组的paper,已经解释的非常好了。
正是因为2.8中的数组背后的实现要与java保持一致,使得通过泛型创建某个数组时
def test[T]() {
val a = new Array[T] //运行时类型缺失,无法创建对应的java数组
}
因为java里的数组,实际会对应int[]
,float[]
,char[]
,等8个基础类型数组,与1个支持协变的引用类型数组。
更细的看一下java引用类型的数组类型:
scala> class A {
| val a1 = new Array[java.lang.Object](1);
| val a2 = new Array[String](1);
| }
scala> :javap A
...
// a1:[Ljava/lang/Object;
...
// a2:[Ljava/lang/String;
看到String[]
会被编译器翻译为[Ljava/lang/String
,而Object[]
被翻译为[Ljava/lang/Object
,其实可以看做是一种泛型类型的特例,[L
是类型构造器,String
、Object
等是类型参数。但不同于java里其他泛型集合的实现,数组类型中的类型参数在运行时是必须的,即 [Ljava/lang/String
与 [Ljava/lang/Object
是两个不同的类型,不像 Collection<String>
与 Collection<StringBuilder>
运行时参数类型都被擦拭掉,成为Collection<Object>
。
为此,2.8中不得不引入了Manifest
来保存T在运行时的类型信息(参考上一篇对Manifest与TypeTag的介绍),从而保证运行时scala数组与java的数组实现一致。
// 补充一段采访
Artima:您能否就Java的协变数组详细说明一下该问题?
Martin Odersky:当Java刚出现时,Bill Joy和James Gosling以及其他Java组成员都认为,Java应该有泛型,只是他们没有足够的时间做出详细设计。所以由于Java中没有泛型,至少最初阶段没有,他们就认为,数组不得不是协变的。例如,这意味着一个字符串(String)数组是一个对象(Object)数组的子类型。其原因是他们希望能够重写,比如,一个“通用”排序方法,采用了一个对象数组和一个用来排序该数组的比较器,然后让你传送一个字符串数组的参数给它。通常情况下这属于类型不健全。这就是为什么在Java中你会获得一个数组存储例外。这实际上也证明,这种同样的事情引起了对于数组泛型实现的需求。这就是为什么在Java中泛型并不好使。你不能定义一个字符串的列表数组,这是不可能的。你只能被迫使用难看的原始类型,永远都只能是一个列表数组。因此,这有点类似原罪。他们对此做出了非常迅速的回应,认为这是一个快速破解。但随后实际上每一个设计决定都被毁灭了。因此,为了不陷入同样的陷阱,我们不得不中断,并提出现在我们将不向上兼容Java,我们也想做一些不同的事情。
http://www.ueren.com/person/view_130304.shtml
这边访谈的中文翻译修正过:
https://www.zhihu.com/question/54160642