例如为什么为什么数组不变,但列表协变?
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”类型中没有“更新”方法)。使用'Array's,这将是一个有效的反例,你可以做什么,数组是协变的。 – Dirk
是的,对不起。我已经更新了 –
信用评级应该归于@sshannin,因为我只是举了一个例子,并重申了他所说的话。 –
这是因为列表是不可变的,数组是可变的。
这个功劳应该归功于@sshannin,因为我只是举了一个例子,并重申了他所说的话。 –
-1。为什么人们喜欢这样的答案?如果没有解释为什么这是相关的,你可能会说“因为'Array'以'A'开头,'List'以'L'开头”。 –
这是一个很好的观点,特拉维斯。在阅读您的评论之前,我提出了这个建议,然后意识到我只赞成它,因为当我到达这里时我已经知道了答案,而且这个答案简洁明了。但只有在你已经知道答案的情况下,这些答案才有用。 – csjacobs24
不同的是,List
s为不可变的,而Array
s为可变的。
要理解为什么可变性决定差异,请考虑制作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
)。
然后,定义接受动物的一个可变的列表,并修改了该列表的功能:
val cats = MutableList[Cat](cat1, cat2)
val horror = mindlessFunc(cats)
:
def mindlessFunc(xs: MutableList[Animal]) = {
xs += new Dog()
}
现在,如果你通过猫的列表到函数可怕的事情会发生
如果我们使用的是粗心的编程语言,编译期间将忽略它。然而,我们的世界会不会崩溃,如果我们只能访问使用下面的代码猫的列表:
cats.foreach(c => c.makeSound)
但是,如果我们这样做:
cats.foreach(c => c.jump)
将出现运行错误。在Scala中,编写这样的代码是被阻止的,因为编译器会抱怨。
这并不回答这个问题,实际上并没有提到数组。 OP可能推断出问题来自数组的可变性,但这里没有说明。 – csjacobs24
@ csjacobs24我在答案的顶部添加了一行以直接回答问题。原始答案的目的是解释为什么可变列表是协变的。 –
@YuhuanJiang好的答案,它可以更好地扩展它,以解释当列表不可变时问题不会发生(默认)。同样在答案的开头,你可以从解释liskov原理的不知情人士的真正协变开始。这个重要问题的答案没有一个是完整的。 – Rpant
给出的正常答案是可变性与协方差相结合会破坏类型安全性。对于收藏品,这可以被视为一个基本事实。但是这个理论实际上适用于任何泛型类型,不仅仅是像List
和Array
这样的集合,我们也不需要尝试和推理可变性。
真正的答案与函数类型与子类型相互作用的方式有关。简言之,如果一个类型参数被用作返回类型,它是协变的。另一方面,如果一个类型参数被用作参数类型,它是逆变的。如果它既用作返回类型又用作参数类型,则它是不变的。
我们来看看documentation for Array[T]
。看的两个明显的方法中的是用于那些用于查找和更新:
def apply(i: Int): T
def update(i: Int, x: T): Unit
在第一种方法是T
返回类型,而在第二T
一种说法类型。变异规则决定了因此T
必须是不变的。
我们可以比较documentation for List[A]
,看看它为什么是协变的。令人困惑的是,我们会发现这些方法,这是类似于方法Array[T]
:
def apply(n: Int): A
def ::(x: A): List[A]
由于A
既用作返回类型和参数类型,我们希望A
是不变的,就像T
是为Array[T]
。然而,与Array[T]
不同,文档向我们介绍了::
的类型。对于大多数这种方法的调用来说,这种说法是足够好的,但不足以决定A
的方差。如果我们扩大这个方法的文档,然后单击“全部签名”中,我们显示了一个道理:
def ::[B >: A](x: B): List[B]
所以A
实际上不会出现的参数类型。相反,B
(可以是任何超类型A
)是参数类型。这对A
没有任何限制,所以它确实可以是协变的。 List[A]
的A
作为参数类型的任何方法是类似的谎言(我们可以告诉,因为这些方法被标记为[use case]
)。
请注意,java数组是协变的,这可能会导致从scala调用java代码时出现问题。 – incrop
@incrop - 你能举个例子吗? –