查看List.scala的来源:
sealed abstract class List[+A] extends ...
...
def isEmpty: Boolean
def head: A
def tail: List[A]
List[+A]
基于+A
是协变的。这是否意味着可以创建一个List[T]
,其中T可以是类型本身,也可以是其任何子类?例:
scala> trait Kid
defined trait Kid
scala> case class Boy(name: String) extends Kid
defined class Boy
scala> case class Girl(name: String) extends Kid
defined class Girl
scala> val list: List[Kid] = List(Boy("kevin"), Girl("sally"))
list: List[Kid] = List(Boy(kevin), Girl(sally))
观察
head
和tail
的类型分别是A
和List[A]
。一旦我们定义了List[+A]
,那么head
和tail
的A
也是协变的吗?我已经阅读了3到4次StackOverflow answer,但我还不了解。
最佳答案
您的示例与方差无关。而且,头和尾也与方差无关。
scala> val list: List[Kid] = List(Boy("kevin"), Girl("sally"))
list: List[Kid] = List(Boy(kevin), Girl(sally))
即使
List
不是协变的,这也可以工作,因为Scala会自动推断出Boy
和Girl
的常见超类型,即Kid
,而右侧的表达式类型将是List[Kid]
,这正是您在左侧所需要的。但是,以下命令不起作用,因为
java.util.List
不是协变的(因为它是Java类型,所以是不变的):scala> import java.util.{List => JList, Arrays}
import java.util.{List=>JList, Arrays}
scala> trait Kid
defined trait Kid
scala> case class Boy(name: String) extends Kid
defined class Boy
scala> val list1 = Arrays.asList(Boy("kevin"), Boy("bob"))
list1: java.util.List[Boy] = [Boy(kevin), Boy(bob)]
scala> val list2: JList[Kid] = list1
<console>:12: error: type mismatch;
found : java.util.List[Boy]
required: java.util.List[Kid]
Note: Boy <: Kid, but Java-defined trait List is invariant in type E.
You may wish to investigate a wildcard type such as `_ <: Kid`. (SLS 3.2.10)
val list2: JList[Kid] = list1
^
Arrays.asList
方法具有如下签名:def asList[T](args: T*): java.util.List[T]
由于
java.util.List[T]
是不变的,因此无法将JList[Boy]
(list1
)分配给JList[Kid]
(list2
)。这是有原因的:如果可以,则因为JList
是可变的,所以您还可以将扩展Kid
(不仅是Boy
)的任何内容添加到同一列表中,从而破坏类型安全性。另一方面,
scala.List
将在完全相同的情况下工作:scala> val list1 = List(Boy("kevin"), Boy("bob"))
list1: List[Boy] = List(Boy(kevin), Boy(bob))
scala> val list2: List[Kid] = list1
list2: List[Kid] = List(Boy(kevin), Boy(bob))
这是因为
scala.List
在其type参数中是协变的。请注意,协变量List
类型的工作方式类似于List[Boy]
是List[Kid]
的子类型,这与您可以将所有内容分配给Any
类型的变量的情况非常相似,因为其他所有类型都是Any
的子类型。这是非常有用的类比。逆差的工作方式非常相似,但方向相反。考虑以下特征:
trait Predicate[-T] {
def apply(obj: T): Boolean
}
object Predicate {
// convenience method to convert functions to predicates
def apply[T](f: (T) => Boolean) = new Predicate[T] {
def apply(obj: T) = f(obj)
}
}
请注意
-
参数之前的T
:这是一个协变量注解,即Predicate[T]
在其唯一的类型参数中定义为是协变量的。回想一下,对于协变列表
List[Boy]
是List[Kid]
的子类型。好吧,对于反谓词,它的工作方式相反:Predicate[Kid]
是Predicate[Boy]
的子类型,因此您可以将Predicate[Kid]
类型的值分配给Predicate[Boy]
类型的变量:scala> val pred1: Predicate[Kid] = Predicate { kid => kid.hashCode % 2 == 0 }
pred1: Predicate[Kid] = Predicate$$anon$1@3bccdcdd
scala> val pred2: Predicate[Boy] = pred1
pred2: Predicate[Boy] = Predicate$$anon$1@3bccdcdd
如果
Predicate[T]
不是协变的,我们将无法将pred1
分配给pred2
,尽管它是完全合法和安全的:显然,在超类型上定义的谓词可以轻松地在子类型上使用。简而言之,方差会影响参数化类型之间的类型兼容性。
List
是协变的,因此您可以将List[Boy]
类型的值分配给List[Kid]
类型的变量(实际上,对于扩展T
的任何S
,您都可以将List[T]
类型的值分配给List[S]
类型的变量)。另一方面,由于
Predicate
是互变的,因此可以将Predicate[Kid]
分配给Predicate[Boy]
(即,对于扩展T
的任何S
,您都可以将Predicate[S]
类型的值分配给Predicate[T]
类型的变量)。如果类型的类型参数不变,则上述两种方法均无法完成(如
JList
所示)。注意参数化类型与其参数之间的对应关系:
T <: S ===> List [T] <: List [S] (covariance)
T <: S ===> Predicate[S] <: Predicate[T] (contravariance)
这就是为什么第一个效果称为* co * variance(左边的
T <: S
和右边是
..T.. <: ..S..
),第二个是* contra * variance(左边是T <: S
,右边是..S.. <: ..T..
)。使自己的参数化类型是协变还是逆变还是不变取决于您的类职责。如果只返回通用类型的值,则使用协方差是有意义的。例如,
List[T]
仅包含返回T
的方法,从不接受T
作为参数,因此可以使其变数以提高表达能力是安全的。这样的参数化类型可以称为生产者。如果您的类仅接受泛型类型的值作为参数,而不返回它们(就像上面的
Predicate
具有单个方法def apply(obj: T): Boolean
一样),那么可以放心地使其成为互变量。这样的参数化类型可以称为使用者如果您的类既接受并返回通用类型的值,即它既是生产者又是消费者,那么您别无选择,只能将类保持不变。
这个习惯用法通常称为“PECS”(“生产者
extends
,消费者super
”),因为方差注释是用Java编写的extends
和super
。
关于scala - 了解列表[+ A]的协方差,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21843805/