5.1 容器和类型推演

Scala可以为Java的泛型容器提供类型推演和类型安全性。下面的例子用到了ArrayList。第一个声明使用了显式却冗余的类型,第二个声明则利用了类型推演。

顺便说一下,注意import语句里的下划线,它等价于Java里的星号(*)。这样,键入java.util._,就会导入java.util包中所有的类。如果下划线不是跟在包名后,而是类名后,会导入类的所有成员——等价于Java的static import

SensibleTyping/Generics.scala

  1. import java.util._
  2. var list1 : List[Int] = new ArrayList[Int]
  3. var list2 = new ArrayList[Int]
  4. list2 add 1
  5. list2 add 2
  6. var total = 0
  7. for (val index <- 0 until list2.size()) {
  8. total += list2.get(index)
  9. }
  10. println("Total is " + total)

输出如下:

  1. Total is 3

对于实例化对象的类型,Scala是很警觉的,严禁进行可能引发类型问题的转换③。下面是一个例子,看看Scala在处理泛型上与Java的差异:

③当然,如果调用代码是从Java或其他语言编译而来,Scala对转换就无能为力了。

SensibleTyping/Generics2.scala

  1. import java.util._
  2. var list1 = new ArrayList[Int]
  3. var list2 = new ArrayList
  4. list2 = list1 // Compilation Error

先是创建了一个引用list1,指向ArrayList[Int]的实例。然后,创建另一个引用list2,指向未指定参数类型的ArrayList实例。在幕后,Scala实际上是创建了一个ArrayList[Nothing]的实例。如果尝试将第一个引用赋给第二个,Scala会报出下面的编译错误④:

④等价的Java代码不会有编译错误,但是会引发运行时的ClassCastException

  1. (fragment of Generics2.scala):6: error: type mismatch;
  2. found : java.util.ArrayList[Int]
  3. required: java.util.ArrayList[Nothing]
  4. list2 = list1 // Compilation Error
  5. ^
  6. one error found
  7. !!!
  8. discarding <script preamble>

在Scala里,Nothing是所有类的子类。Scala把new ArrayList当作ArrayList [Nothing]的实例,这样,任何有意义类型的实例都不可能添加到这个容器里。因为基类实例是不能当作派生类实例的,而Nothing是最低层的子类。

如此说来,怎样才能创建一个不指定类型的ArrayList呢?一种方式是使用类型Any。我们已经见识过Scala如何处理持有Nothing类型对象的容器的赋值;然而其他情况有所不同——默认情况下,Scala要求赋值两边的容器类型相同(稍后,在5.7节“参数化类型的可变性”会看到如何改变这一默认行为)。

下面的例子用了Any类型对象的容器——在Scala中,Any是所有类型的基类型:

SensibleTyping/Generics3.scala

  1. import java.util._
  2. var list1 = new ArrayList[Int]
  3. var list2 = new ArrayList[Any]
  4. var ref1 : Int = 1
  5. var ref2 : Any = null
  6. ref2 = ref1 //OK
  7. list2 = list1 // Compilation Error

这次,list1指向了一个ArrayList[Int],而list2指向了一个ArrayList[Any]。随后,还创建了两个引用:ref1,类型是Intref2,类型是Any。将ref1赋给ref2,可以通过Scala的编译。这等价于把一个Integer引用赋给一个Object类型的引用。在默认情况下,Scala是不允许将一个持有任意类型实例的容器赋给一个持有Any实例的容器。(稍后我们会讨论协变(covariance),为这个规则提供一个例外情况。)至此,我们已经见识过,Java泛型是如何享用Scala带来的增强的类型安全的。

想从Scala类型检查获益,就不必到处指定类型。只要是有意义的地方,都可以依赖类型推演。推演发生在编译时。因此,可以肯定的是,类型检查会在编译代码时起作用。

Scala坚持认为无参数化类型的容器是Nothing的容器,并且限制类型间的赋值。这几点结合起来,会增强编译时的类型安全——为其提供一个自适应的、没有繁文缛节的静态类型。

在上面的例子里,我们用到了Java的容器。Scala也提供了丰富的容器,我们会在第8章“使用容器”中介绍。