5.1 容器和类型推演
Scala可以为Java的泛型容器提供类型推演和类型安全性。下面的例子用到了ArrayList
。第一个声明使用了显式却冗余的类型,第二个声明则利用了类型推演。
顺便说一下,注意import
语句里的下划线,它等价于Java里的星号(*
)。这样,键入java.util._
,就会导入java.util
包中所有的类。如果下划线不是跟在包名后,而是类名后,会导入类的所有成员——等价于Java的static import
:
SensibleTyping/Generics.scala
import java.util._
var list1 : List[Int] = new ArrayList[Int]
var list2 = new ArrayList[Int]
list2 add 1
list2 add 2
var total = 0
for (val index <- 0 until list2.size()) {
total += list2.get(index)
}
println("Total is " + total)
输出如下:
Total is 3
对于实例化对象的类型,Scala是很警觉的,严禁进行可能引发类型问题的转换③。下面是一个例子,看看Scala在处理泛型上与Java的差异:
③当然,如果调用代码是从Java或其他语言编译而来,Scala对转换就无能为力了。
SensibleTyping/Generics2.scala
import java.util._
var list1 = new ArrayList[Int]
var list2 = new ArrayList
list2 = list1 // Compilation Error
先是创建了一个引用list1
,指向ArrayList[Int]
的实例。然后,创建另一个引用list2
,指向未指定参数类型的ArrayList
实例。在幕后,Scala实际上是创建了一个ArrayList[Nothing]
的实例。如果尝试将第一个引用赋给第二个,Scala会报出下面的编译错误④:
④等价的Java代码不会有编译错误,但是会引发运行时的
ClassCastException
。
(fragment of Generics2.scala):6: error: type mismatch;
found : java.util.ArrayList[Int]
required: java.util.ArrayList[Nothing]
list2 = list1 // Compilation Error
^
one error found
!!!
discarding <script preamble>
在Scala里,Nothing
是所有类的子类。Scala把new ArrayList
当作ArrayList [Nothing]
的实例,这样,任何有意义类型的实例都不可能添加到这个容器里。因为基类实例是不能当作派生类实例的,而Nothing
是最低层的子类。
如此说来,怎样才能创建一个不指定类型的ArrayList
呢?一种方式是使用类型Any
。我们已经见识过Scala如何处理持有Nothing
类型对象的容器的赋值;然而其他情况有所不同——默认情况下,Scala要求赋值两边的容器类型相同(稍后,在5.7节“参数化类型的可变性”会看到如何改变这一默认行为)。
下面的例子用了Any
类型对象的容器——在Scala中,Any
是所有类型的基类型:
SensibleTyping/Generics3.scala
import java.util._
var list1 = new ArrayList[Int]
var list2 = new ArrayList[Any]
var ref1 : Int = 1
var ref2 : Any = null
ref2 = ref1 //OK
list2 = list1 // Compilation Error
这次,list1
指向了一个ArrayList[Int]
,而list2
指向了一个ArrayList[Any]
。随后,还创建了两个引用:ref1
,类型是Int
;ref2
,类型是Any
。将ref1
赋给ref2
,可以通过Scala的编译。这等价于把一个Integer
引用赋给一个Object
类型的引用。在默认情况下,Scala是不允许将一个持有任意类型实例的容器赋给一个持有Any
实例的容器。(稍后我们会讨论协变(covariance),为这个规则提供一个例外情况。)至此,我们已经见识过,Java泛型是如何享用Scala带来的增强的类型安全的。
想从Scala类型检查获益,就不必到处指定类型。只要是有意义的地方,都可以依赖类型推演。推演发生在编译时。因此,可以肯定的是,类型检查会在编译代码时起作用。
Scala坚持认为无参数化类型的容器是Nothing
的容器,并且限制类型间的赋值。这几点结合起来,会增强编译时的类型安全——为其提供一个自适应的、没有繁文缛节的静态类型。
在上面的例子里,我们用到了Java的容器。Scala也提供了丰富的容器,我们会在第8章“使用容器”中介绍。