Scala与Java之间的型变对比

就如之前所说的那样,Scala的型变的行为定义在Scala类型的声明处,而Java则是在调用处. 类型的委托(client)定义型变通常默认是不变类型. Java不允许你在定义处指定型变的行为,尽管你可以使用类似的表达式.这些表达式定义了类型绑定,这些我们待会讨论.

Java的在调用出调用两个大的弊端. 首先,它应该是类库设计者的工作,设计者了解正确的型变行为而且能够编码出和类库本身的行为. 确实会更加类库使用者的负担. 这就导致了第二个弊端. 对于Java使用者来说使用不正确的类型注释是很容易的,由此可能会导致不安全的代码.

在Java类型系统中的另一个问题则是Arrays是协变的,对于每个类型T而言. 考虑下面这个例子.

public class JavaArrays {
    public static void main(String[] args) {
        Integer[] array1 = new Integer[]{
                new Integer(1), new Integer(2), new Integer(3)};
        Number[] array2 = array1; // Compiles fine
        array2[2] = new Double(3.14); // Compiles, but throws a runtime error!
    }
}


//Exception in thread "main" java.lang.ArrayStoreException: java.lang.Double at
//JavaArrays.main(JavaArrays.java:6)

怎么回事?我们之前讨论过,可变及集合必须是不变类型才是安全的.因为Java 数组是协变类型,所以编译器允许吧Array[Integer]实例赋值给Array[Number]引用.然后编译器认为把任意的Number赋值给数组中的元素是Ok的,但是事实上来说,array在 “内部”知道它只接受Integer值(如果可以的话,还有该值的子类型信息),所以它就抛出了运行时异常,破坏了静态类型检查的功能.注意,尽管Scala封装了Java Array类,但是Scala 类Scala.Array是不变类型,所以它就避免了这个 “洞”.

可以看下Maurice Naftalin and Philip Wadler, Java Generics and Collections, O’Reilly Media,2006 这本书关于Java泛型的和数组的详细细节.

编辑于 2019-12-11

文章被以下专栏收录