2011-07-13 32 views
43

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

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

工作,但

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

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

+7

请注意,java数组是协变的,这可能会导致从scala调用java代码时出现问题。 – incrop

+0

@incrop - 你能举个例子吗? –

回答

64

因为否则它会破坏类型安全。 如果没有,你将能够做这样的事情:

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

和编译器不能抓住它。

在另一方面,名单是不可改变的,所以你不能添加的东西是不是Int

+1

你的意思是'Array',而不是'List',是吗?使用列表,您的示例将不起作用(“List”类型中没有“更新”方法)。使用'Array's,这将是一个有效的反例,你可以做什么,数组是协变的。 – Dirk

+0

是的,对不起。我已经更新了 –

+1

信用评级应该归于@sshannin,因为我只是举了一个例子,并重申了他所说的话。 –

28

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

+3

这个功劳应该归功于@sshannin,因为我只是举了一个例子,并重申了他所说的话。 –

+12

-1。为什么人们喜欢这样的答案?如果没有解释为什么这是相关的,你可能会说“因为'Array'以'A'开头,'List'以'L'开头”。 –

+1

这是一个很好的观点,特拉维斯。在阅读您的评论之前,我提出了这个建议,然后意识到我只赞成它,因为当我到达这里时我已经知道了答案,而且这个答案简洁明了。但只有在你已经知道答案的情况下,这些答案才有用。 – csjacobs24

4

不同的是,List s为不可变的,而Array s为可变的。

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

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中,编写这样的代码是被阻止的,因为编译器会抱怨。

+0

这并不回答这个问题,实际上并没有提到数组。 OP可能推断出问题来自数组的可变性,但这里没有说明。 – csjacobs24

+1

@ csjacobs24我在答案的顶部添加了一行以直接回答问题。原始答案的目的是解释为什么可变列表是协变的。 –

+0

@YuhuanJiang好的答案,它可以更好地扩展它,以解释当列表不可变时问题不会发生(默认)。同样在答案的开头,你可以从解释liskov原理的不知情人士的真正协变开始。这个重要问题的答案没有一个是完整的。 – Rpant

4

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

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

我们来看看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])。

相关问题