我在Scala中使用集合,发现可变集合被定义为不变量,不可变集合被定义为协变。 Scala中方差与可变性/不变性之间的关系是什么?
class Array[T]
class List[+T]
答案 0 :(得分: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仍然不允许您为所有内容分配mx。要理解为什么,假设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是不可变的。您可以向xs List添加1,它将返回一个类型为Any的新列表。
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通过强制参数类型为逆变或不变来在编译时停止这些错误。
希望这有帮助。
答案 1 :(得分: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)