例如,为什么
val list:List[Any] = List[Int](1,2,3)
工作,但是
val arr:Array[Any] = Array[Int](1,2,3)
因为数组是不变的,所以它失败了。这个设计决策的期望效果是什么?
例如,为什么
val list:List[Any] = List[Int](1,2,3)
工作,但是
val arr:Array[Any] = Array[Int](1,2,3)
因为数组是不变的,所以它失败了。这个设计决策的期望效果是什么?
否则会破坏类型安全。如果不这样,你将能够执行这样的操作:
val arr:Array[Int] = Array[Int](1,2,3)
val arr2:Array[Any] = arr
arr2(0) = 2.54
而编译器无法捕获它。
另一方面,列表是不可变的,因此您不能添加不是Int
类型的内容。
Array
,而不是 List
,对吗?使用列表,你的示例将无法工作(类型 List
中没有“update”方法)。如果数组是协变的,那么它将是一个有效的反例,说明你可以做什么。 - Dirkarr2(0)
计算结果为2.54
,@OpDeCirkel? - Kevin MeredithSet
,虽然可以安全地向上转型,但在其类型上也是不变的 - 这是因为 Set[A]
也是一个 A => Boolean
,但函数在其参数上是逆变的。Set
可以通过 Set[Child]().asInstanceOf[Set[Parent]]
或 Set[Child]().toSet[Parent]
进行向上转型 - 第一个方法不太好看,但不会创建新的集合。 - Sergey这是因为列表(lists)是不可变(immutable)的,而数组(arrays)是可变(mutable)的。
Array
以'A'开头,而List
以'L'开头”也同样无意义。 - Travis BrownList
和 Array
这样的集合,并且我们根本不必考虑可变性。Array[T]
的文档。需要关注的两种明显的方法是查找和更新方法:def apply(i: Int): T
def update(i: Int, x: T): Unit
T
是返回类型,而在第二种方法中,T
是参数类型。协变的规则指定 T
必须是不变的。List[A]
的文档 来看为什么它是协变的。令人困惑的是,我们会发现这些方法与 Array[T]
的方法类似:def apply(n: Int): A
def ::(x: A): List[A]
由于A
既用作返回类型又用作参数类型,我们期望A
像Array[T]
中的T
一样是不变的。然而,与Array[T]
不同的是,文档在关于::
的类型上是在撒谎。这个谎言对于大多数对该方法的调用来说已经足够了,但无法足以决定A
的变异性。如果我们扩展此方法的文档并单击“完整签名”,我们将看到实际情况:
def ::[B >: A](x: B): List[B]
A
实际上并没有出现作为参数类型。相反,B
(可以是 A
的任何超类型)是参数类型。这不会对 A
产生任何限制,因此它实际上可以是协变的。在 List[A]
上有 A
作为参数类型的任何方法都是类似的谎言(我们可以看到这些方法被标记为 [use case]
)。::
的真实签名部分是无价的。 - Ashkan Kh. Nazary2.8
开始,决定让读者免于在集合 API 中处理类型注释的微妙之处,只显示涵盖绝大多数用例的更温和的签名。这就是为什么文档中说 ...[A]
而不是 ...[B >: A]
,仅仅作为更易读的辅助说明。这并不意味着实际的方法签名是更简单的那个。实际的方法签名仍然是具有正确变化性的更复杂的那个。 - Ashkan Kh. Nazary区别在于List
是不可变的,而Array
是可变的。
要理解为什么可变性决定了方差,考虑创建一个可变版本的List
- 让我们称之为MutableList
。我们还将使用一些示例类型:一个基类Animal
和两个子类分别命名为Cat
和Dog
。
trait Animal {
def makeSound: String
}
class Cat extends Animal {
def makeSound = "meow"
def jump = // ...
}
class Dog extends Animal {
def makeSound = "bark"
}
请注意,Cat
比Dog
多了一个方法(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中,编译器会提示,从而避免这种情况的发生。