考虑一个Scala收集器,例如List
或一个特性,例如GenTraversible
和GenTraversibleLike
。所有这些都是使用A
类型定义的,因此它是变体(例如List[+A]
)。
这些类型/特征具有某些方法,例如contains(elem: A)
和filter(pred: A => Boolean)
。
我不明白为什么允许这样做 - 似乎类型A
出现在逆变位置,即使类/特征声明显示+A
(变体)。
答案 0 :(得分:4)
A
处于contains
和filter
的协变位置。
contains
的签名实际上是:
def contains[A1 >: A](elem: A1): Boolean
当在较低类型边界的右侧使用类型变量时,它处于协变位置。
使用filter
,参数的类型为A => Boolean
,即Function1[A, Boolean]
。 Function1
在其第一个参数中是逆变的,Function1
本身在逆变位置作为filter
的参数。两个逆变相结合,使协方差成为可能。
了解这一点的一种方法是List[X]
X <: Y
。如果将此List[X]
转换为List[Y]
,这些方法是否仍然是类型安全的? contains
现在要求A1
是Y
的超类型,这实际上是一个更严格的要求。 filter
现在需要一个函数Y => Boolean
,这也是一个更严格的要求,因为传入的函数必须能够处理任何Y
,而不仅仅是那些{{1}的实例}。另一方面,将X
投射到List[Y]
不是类型安全的。如果List[X]
也是子类型X
但Z
没有,Y
用于Z
A1
,那么类型绑定将被违反,因为contains
不是真的。对于过滤器,传递的函数将为Z >: Y
,并且无法安全地传递X => Boolean
包含的Y
实例。因此,我们可以得出结论:List
确实在这些方法中具有协变性而非逆变性。
答案 1 :(得分:1)
这是允许的,因为对于协变类型,带参数的方法可以具有T的下限。请考虑以下示例:
trait myList[+T] {
def prepend(elem: T): List[T] = new Cons(elem, this)
}
这不起作用,因为您不能将协变类型作为参数 - 您将收到编译时错误。另一方面,您可以使用T的下限限制方法参数 - 参数可以是T或T的超类型,而不是子类型,如此
trait myList[+T] {
def prepend[S >: T](elem: S): List[S] = new Cons(elem, this)
}