我在 SIA 找到了一个简单的解释。以下是直接从那里开始的。
可变对象需要保持不变
当类型参数既不是协变也不是逆变时,它就是不变的。所有 Scala 可变集合类都是不变的。一个例子可以解释为什么可变对象需要保持不变。由于 ListBuffer 是可变的,因此将其声明为不变式,如下所示:
final class ListBuffer[A] ...{ ... }
因为它被声明为不变的,所以您不能将 ListBuffer 从一种类型分配给另一种类型。以下代码将引发编译错误:
scala> val mxs: ListBuffer[String] = ListBuffer("pants")
mxs: scala.collection.mutable.ListBuffer[String] =
ListBuffer(pants)
scala> val everything: ListBuffer[Any] = mxs
<console>:6: error: type mismatch;
found : scala.collection.mutable.ListBuffer[String]
required: scala.collection.mutable.ListBuffer[Any]
val everything: ListBuffer[Any] = mxs
尽管 String 是 scala.Any 的子类型,Scala 仍然不允许您将 mxs 分配给所有内容。要理解原因,假设 ListBuffer 是协变的,并且以下代码片段可以正常工作,不会出现任何编译问题:
scala> val mxs: ListBuffer[String] = ListBuffer("pants")
mxs: scala.collection.mutable.ListBuffer[String] =
ListBuffer(pants)
scala> val everything: ListBuffer[Any] = mxs
scala> everything += 1
res4: everything.type = ListBuffer(1, pants)
你能找出问题所在吗?因为所有内容都是 Any 类型,所以您可以将整数值存储到字符串集合中。这是一场即将发生的灾难。这正是 Java 数组所发生的情况。为了避免此类问题,使可变对象保持不变总是一个好主意。下一个问题是集合的不可变对象会发生什么。事实证明,对于不可变对象,协方差根本不是问题。如果用不可变的 List 替换 ListBuffer,则可以获取 List[String] 的实例并将其分配给 List[Any],不会出现任何问题。
scala> val xs: List[String] = List("pants")
xs: List[String] = List(pants)
scala> val everything: List[Any] = xs
everything: List[Any] = List(pants)
此分配安全的唯一原因是 List 是不可变的。你可以向 xs List 添加 1,它将返回一个新的 Any 类型的 List。
scala> 1 :: xs
res5: List[Any] = List(1, pants)
同样,这种添加是安全的,因为 cons(::) 方法总是返回一个新的 List,并且其类型由 List 中元素的类型决定。唯一可以存储整数值和引用值的类型是 scala.Any。在处理可变/不可变对象时,这是一个需要记住的关于类型差异的重要属性。
理解逆变的最好方法是看看逆变不存在时会出现的问题。尝试在以下 Java 代码示例中找出问题:
Object[] arr = new int[1];
arr[0] = "Hello, there!";
您最终将字符串分配给整数数组。 Java 通过抛出 ArrayStoreException 在运行时捕获此错误。 Scala 通过强制参数类型为逆变或不变来在编译时阻止此类错误。
希望这可以帮助。