为什么数组是不变的,而列表是协变的?
例如,为什么可以
val list:List[Any] = List[Int](1,2,3)
工作,但
val arr:Array[Any] = Array[Int](1,2,3)
会失败(因为数组是不变的)。这个设计决策背后的预期效果是什么?
E.g. why does
val list:List[Any] = List[Int](1,2,3)
work, but
val arr:Array[Any] = Array[Int](1,2,3)
fails (because arrays are invariant). What is the desired effect behind this design decision?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
因为否则它会破坏类型安全。
如果没有,您将能够执行以下操作:
并且编译器无法捕获它。
另一方面,列表是不可变的,因此您不能添加非
Int
的内容Because it would break type-safety otherwise.
If not, you would be able to do something like this:
and the compiler can't catch it.
On the other hand, lists are immutable, so you can't add something that is not
Int
这是因为列表是不可变的,而数组是可变的。
This is because lists are immutable and arrays are mutable.
通常给出的答案是,可变性与协变相结合会破坏类型安全。对于收藏来说,这可以作为一个基本真理。但该理论实际上适用于任何泛型类型,而不仅仅是像
List
和Array
这样的集合,而且我们根本不需要尝试和推理可变性。真正的答案与函数类型与子类型交互的方式有关。简而言之,如果类型参数用作返回类型,那么它是协变的。另一方面,如果类型参数用作参数类型,则它是逆变的。如果它既用作返回类型又用作参数类型,则它是不变的。
让我们看一下 Array[T] 的文档 。要查看的两个明显的方法是用于查找和更新的方法:
在第一个方法中
T
是返回类型,而在第二个方法中T
是参数类型。因此,方差规则规定T
必须是不变的。我们可以比较 List[A] 的文档 看看为什么它是协变的。令人困惑的是,我们会发现这些方法类似于 Array[T] 的方法:
由于
A
既用作返回类型又用作参数类型,因此我们期望 A 是不变的,就像 Array[T] 的 T 一样。然而,与Array[T]
不同的是,文档在::
的类型上对我们撒了谎。对于大多数对此方法的调用来说,谎言已经足够好了,但不足以决定A
的方差。如果我们展开此方法的文档并单击“完整签名”,我们就会看到真相:因此
A
实际上并不显示为参数类型。相反,B
(可以是A
的任何超类型)是参数类型。这不会对A
施加任何限制,因此它确实可以是协变的。List[A]
上以A
作为参数类型的任何方法都是类似的谎言(我们可以看出,因为这些方法被标记为[use case]< /代码>)。
The normal answer to give is that mutability combined with covariance would break type safety. For collections, this can be taken as a fundamental truth. But the theory actually applies to any generic type, not just collections like
List
andArray
, and we don't have to try and reason about mutability at all.The real answer has to do with the way function types interact with subtyping. The short story is if a type parameter is used as a return type, it is covariant. On the other hand, if a type parameter is used as an argument type, it is contravariant. If it is used both as a return type and as an argument type, it is invariant.
Let's look at the documentation for
Array[T]
. The two obvious methods to look at are for the ones for lookup and update:In the first method
T
is a return type, while in the secondT
is an argument type. The rules of variance dictate thatT
must therefore be invariant.We can compare the documentation for
List[A]
to see why it is covariant. Confusingly, we would find these methods, which are analogous to the methods forArray[T]
:Since
A
is used as both a return type and as an argument type, we would expectA
to be invariant just likeT
is forArray[T]
. However, unlike withArray[T]
, the documentation is lying to us about the type of::
. The lie is good enough for most calls to this method, but isn't good enough to decide the variance ofA
. If we expand the documentation for this method and click on "Full Signature", we are shown the truth:So
A
does not actually appear as an argument type. Instead,B
(which can be any supertype ofA
) is the argument type. This does not place any restriction onA
, so it really can be covariant. Any method onList[A]
which hasA
as an argument type is a similar lie (we can tell because these methods are marked as[use case]
).区别在于
List
是不可变的,而Array
是可变的。要理解为什么可变性决定方差,请考虑制作
List
的可变版本 - 我们称之为MutableList
。我们还将使用一些示例类型:一个基类Animal
和 2 个名为Cat
和Dog
的子类。请注意,
Cat
比Dog
多了一种方法 (jump
)。然后,定义一个接受可变动物列表并修改该列表的函数:
现在,如果将猫列表传递到该函数中,将会发生可怕的事情:
如果我们使用粗心的编程语言,这将在编译过程中被忽略。尽管如此,如果我们只使用以下代码访问猫列表,我们的世界不会崩溃:
但如果我们这样做:
就会发生运行时错误。使用 Scala,可以防止编写此类代码,因为编译器会抱怨。
The difference is that
List
s are immutable whileArray
s are mutable.To understand why mutability determines variance, consider making a mutable version of
List
- let's call itMutableList
. We'll also make use of some example types: a base classAnimal
and 2 subclasses namedCat
andDog
.Notice that
Cat
has one more method (jump
) thanDog
.Then, define a function that accepts a mutable list of animals and modifies the list:
Now, horrible things will happen if you pass a list of cats into the function:
If we were using a careless programming language, this will be ignored during compilation. Nevertheless, our world will not collapse if we only access the list of cats using the following code:
But if we do this:
A runtime error will occur. With Scala, writing such code is prevented, because the compiler will complain.