查看List.scala的源代码:
sealed abstract class List[+A] extends ...
...
def isEmpty: Boolean
def head: A
def tail: List[A]
Run Code Online (Sandbox Code Playgroud)
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))
Run Code Online (Sandbox Code Playgroud)
分别观察head和tail类型是A和List[A].一旦我们定义了List[+A],那么head和tails A也是协变的?
我已经读过这个StackOverflow的答案 3或4次,但我还不明白.
您的示例与方差无关.而且,头部和尾部也与方差无关.
scala> val list: List[Kid] = List(Boy("kevin"), Girl("sally"))
list: List[Kid] = List(Boy(kevin), Girl(sally))
Run Code Online (Sandbox Code Playgroud)
这是否会甚至工作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
^
Run Code Online (Sandbox Code Playgroud)
Arrays.asList 方法有这样的签名:
def asList[T](args: T*): java.util.List[T]
Run Code Online (Sandbox Code Playgroud)
由于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))
Run Code Online (Sandbox Code Playgroud)
那是因为scala.List它的类型参数是协变的.请注意,协变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)
}
}
Run Code Online (Sandbox Code Playgroud)
注意-before 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
Run Code Online (Sandbox Code Playgroud)
如果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)
Run Code Online (Sandbox Code Playgroud)
这就是为什么第一个效果被称为*co*方差(T <: S在左边和
..T.. <: ..S..右边),第二个是*contra*方差(T <: S在左边,但..S.. <: ..T..在右边)的原因.
是否使自己的参数化类型协变或逆变或不变取决于您的类职责.如果它只返回泛型类型的值,那么使用协方差是有意义的.List[T]例如,仅包含返回的方法T,从不接受T作为参数,因此为了增加表现力而使其协变是安全的.这种参数化类型可以称为生产者.
如果你的类只接受泛型类型的值作为参数,不返回它们(完全像Predicate上面有单个方法def apply(obj: T): Boolean),那么你可以安全地使它逆变.这种参数化类型可以称为消费者
如果您的类既接受并返回泛型类型的值,即它既是生产者又是消费者,那么您别无选择,只能将该类保持在此泛型类型参数中.
这个成语通常被称为"佩奇"("生产者extends,消费者super"),因为方差批注写入extends和superJava编写的.
| 归档时间: |
|
| 查看次数: |
465 次 |
| 最近记录: |