7

我在 Scala 中使用集合,发现可变集合被定义为不可变集合,而不可变集合被定义为协变。Scala中的方差和可变性/不变性之间有什么关系?

class Array[T]

class List[+T]
4

2 回答 2

6

我在 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 数组所发生的事情。为了避免这类问题,让可变对象保持不变总是一个好主意。下一个问题是在集合的不可变对象的情况下会发生什么。事实证明,对于不可变对象,协方差根本不是问题。如果将 ListBuffer 替换为不可变的 List,则可以获取 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 是不可变的。您可以将 1 添加到 xs 列表,它将返回一个任何类型的新列表。

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 在编译时通过强制参数类型是逆变的或不变的来停止这些类型的错误。

希望这可以帮助。

于 2013-09-01T19:32:14.800 回答
4

如果该类型参数仅出现在协变位置,则该类型只能被标记为协变。通常,这意味着类/特征/对象具有返回变体类型值的方法,但没有具有变体类型参数的方法。可变集合始终具有带有变体类型参数的方法,例如update. 想象一下如果Array可以声明 Array[+T] 会发生什么:

val as = Array[String]("a string")

// this statement won't typecheck in actual Scala
val aa: Array[AnyRef] = as

aa(0) = ("I'm a string...", "but this tuple itself isn't!")

// Tuples don't have a substring method, so this would fail at run-time
// if the compiler allowed this code to compile.
as(0).substring(0)
于 2013-09-01T18:02:38.890 回答