为什么数组不变,但列表协变?

为什么呢

val list:List[Any] = List[Int](1,2,3) 

工作,但是

 val arr:Array[Any] = Array[Int](1,2,3) 

失败(因为数组是不变的)。 这个devise决定背后的预期效果是什么?

因为否则会打破型号安全。 如果没有,你可以做这样的事情:

 val arr:Array[Int] = Array[Int](1,2,3) val arr2:Array[Any] = arr arr2(0) = 2.54 

编译器不能捕捉它。

另一方面,列表是不可变的,所以你不能添加一些不是Int东西

这是因为列表是不可变的,数组是可变的。

区别在于List是不可变的,而Array是可变的。

要理解为什么可变性决定了方差,可以考虑制作一个可变版本的List – 我们称之为MutableList 。 我们还将使用一些示例types:一个基类Animal和两个名为CatDog子类。

 trait Animal { def makeSound: String } class Cat extends Animal { def makeSound = "meow" def jump = // ... } class Dog extends Animal { def makeSound = "bark" } 

请注意, CatDog有更多的方法( jump )。

然后,定义一个接受动物列表并修改列表的函数:

 def mindlessFunc(xs: MutableList[Animal]) = { xs += new Dog() } 

现在,如果你把一个猫的列表传入函数,会发生可怕的事情:

 val cats = MutableList[Cat](cat1, cat2) val horror = mindlessFunc(cats) 

如果我们使用粗心的编程语言,编译过程中将忽略它。 不过,如果我们只使用下面的代码访问猫的列表,我们的世界将不会崩溃:

 cats.foreach(c => c.makeSound) 

但是,如果我们这样做:

 cats.foreach(c => c.jump) 

运行时错误将会发生。 在Scala中,编写这样的代码是被阻止的,因为编译器会抱怨。

给出的正常答案是可变性与协方差相结合会破坏types安全性。 对于collections,这可以被视为一个基本事实。 但是这个理论实际上适用于任何genericstypes,而不仅仅是像ListArray这样的集合。

真正的答案与函数types与子types相互作用的方式有关。 简言之,如果一个types参数被用作返回types,它是协变的。 另一方面,如果一个types参数被用作参数types,它是逆变的。 如果它既用作返回types又用作参数types,则它是不变的。

我们来看一下Array[T]的文档 。 两种显而易见的方法是查找和更新:

 def apply(i: Int): T def update(i: Int, x: T): Unit 

在第一个方法中, T是一个返回types,而在第二个T是一个参数types。 变异规则决定了T必须是不变的。

我们可以比较List[A]的文档,看看它为什么是协变的。 令人困惑的是,我们会发现这些方法,类似于Array[T]的方法:

 def apply(n: Int): A def ::(x: A): List[A] 

由于A被用作返回types和参数types,所以我们期望A是不变的,就像TArray[T] 。 但是,与Array[T]不同的是,文档向我们讲述了::的types。 对于大多数这种方法的调用来说,这个谎言已经足够了,但是不足以决定A的方差。 如果我们扩展这个方法的文档,点击“Full Signature”,我们就会看到这个事实:

 def ::[B >: A](x: B): List[B] 

所以A实际上并不是一个参数types。 相反, B (可以是A任何超types)是参数types。 这对A没有任何限制,所以它可以是协变的。 任何具有A作为参数types的List[A]上的方法都是类似的谎言(我们可以看出,因为这些方法被标记为[use case] )。