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

fre*_*oma 51 arrays scala list covariance

例如,为什么呢

val list:List[Any] = List[Int](1,2,3)
Run Code Online (Sandbox Code Playgroud)

工作,但是

val arr:Array[Any] = Array[Int](1,2,3)
Run Code Online (Sandbox Code Playgroud)

失败(因为数组是不变的).这个设计决定背后的效果是什么?

Op *_*kel 73

因为它会破坏类型安全性.如果没有,你可以做这样的事情:

val arr:Array[Int] = Array[Int](1,2,3)
val arr2:Array[Any] = arr
arr2(0) = 2.54
Run Code Online (Sandbox Code Playgroud)

并且编译器无法捕获它.

另一方面,列表是不可变的,因此您无法添加不是的内容 Int


ssh*_*nin 26

这是因为列表是不可变的,并且数组是可变的.

  • -1.人们为什么要赞成这样的答案?如果没有一些解释为什么这是相关的,你可能会说"因为`Array`以'A'开头而'List`以'L'开头". (16认同)
  • 应该归功于@sshannin,因为我只是举了一个例子并重新说明了他所说的话. (3认同)

Ken*_*nde 7

给出的正常答案是可变性与协方差相结合会破坏类型安全。对于集合,这可以被视为一个基本真理。但该理论实际上适用于任何泛型类型,而不仅仅是像ListandArray这样的集合,我们根本不必尝试和推理可变性。

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

让我们看看文档Array[T]。要查看的两个明显方法是用于查找和更新的方法:

def apply(i: Int): T
def update(i: Int, x: T): Unit
Run Code Online (Sandbox Code Playgroud)

第一种方法T是返回类型,而第二种方法T是参数类型。方差规则规定,T因此必须是不变的。

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

def apply(n: Int): A
def ::(x: A): List[A]
Run Code Online (Sandbox Code Playgroud)

由于A既用作返回类型又用作参数类型,我们希望ATfor一样保持不变Array[T]。但是,与 不同Array[T],文档在::. 对于大多数调用此方法而言,谎言已经足够好,但不足以确定 的方差A。如果我们展开此方法的文档并单击“完整签名”,我们就会看到真相:

def ::[B >: A](x: B): List[B]
Run Code Online (Sandbox Code Playgroud)

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


Yuh*_*ang 6

区别在于Lists是不可变的,而Arrays是可变的.

要理解为什么可变性决定方差,考虑制作一个可变版本List- 让我们称之为MutableList.我们还会使用一些示例类型:一个基类Animal和2子命名CatDog.

trait Animal {
  def makeSound: String
}

class Cat extends Animal {
  def makeSound = "meow"
  def jump = // ...
}

class Dog extends Animal {
  def makeSound = "bark"
}
Run Code Online (Sandbox Code Playgroud)

请注意,Cat还有一个方法(jump)比Dog.

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

def mindlessFunc(xs: MutableList[Animal]) = {
  xs += new Dog()
}
Run Code Online (Sandbox Code Playgroud)

现在,如果你将一系列猫传递给函数,会发生可怕的事情:

val cats = MutableList[Cat](cat1, cat2)
val horror = mindlessFunc(cats)
Run Code Online (Sandbox Code Playgroud)

如果我们使用粗心的编程语言,那么在编译期间将忽略它.然而,如果我们只使用以下代码访问猫的列表,我们的世界将不会崩溃:

cats.foreach(c => c.makeSound)
Run Code Online (Sandbox Code Playgroud)

但是如果我们这样做:

cats.foreach(c => c.jump)
Run Code Online (Sandbox Code Playgroud)

将发生运行时错误.使用Scala,可以防止编写此类代码,因为编译器会抱怨.