[Scala] 了解 协变 与 逆变

首先定义一个类 A,其参数类型 T 为协变,类中包含一个方法 func,该方法有一个类型为 T 的参数:

  1 class A[+T] {
  2   def func(x: T) {}
  3 }

此时在 x 处会有异常提示,covariant type T occurs in contravariant position in type T of value x

现在,先假设该类定义编译可以通过
因为 String→AnyRef(String 是 AnyRef 的子类),参数类型 T 为协变,所以 A[String]→A[AnyRef](A[String] 也是 A[AnyRef] 的子类)。
定义两个对象如下:
val father: A[AnyRef] = null.asInstanceOf[A[AnyRef]] // 父类对象,包含方法 func(x: AnyRef)
val child: A[String] = null.asInstanceOf[A[String]] // 子类对象,包含方法 func(x: String)
根据里氏替换原则,调用 father.func(Nil) 里的 father 对象应该可以直接替换成 child 对象,但是 child.func(Nil) 调用显然异常。
或者定义如下一个函数,接收 A[AnyRef] 作为参数:
  1 def otherFunc(x: A[AnyRef]): Unit = {
  2   x.func(Nil)
  3 }
同理调用 otherFunc(father) 里的 father 对象也应该可以直接替换成 child 对象,但是如此一来 otherFunc 中的参数要调用 func 方法就只能传入 String 类型的参数了(因为 child 对象中的 func 方法只能接收 String 类型参数),相当于 otherFunc 的处理范围缩小了,这是不允许的。
也就是说上面 A 类的定义违反了里氏替换原则,所以编译器无法通过。
图解上述分析过程:
e8cdf7ab-5d87-4a66-8a2e-07e63f90899d[4]
反之,如果用 father 的父类对象替换,相当于 otherFunc 的处理范围变大了,此时 otherFunc 中的参数要调用 func 方法,完成可以传入 AnyRef 类型的参数。
  1 val fatherFather: A[Any] = null.asInstanceOf[A[Any]] // func(x: Any)
  2 otherFunc(fatherFather)// 如果 T 为协变,此处会提示异常

总结:

协变点(covariant position)方法返回值的位置;
逆变点(contravariant position)方法参数的位置;
因为 A 中的类型参数 T 声明为协变,而 T 又是 func 方法中的参数类型,属于逆变点,所以此时编译器会提示异常:
covariant type T occurs in contravariant position in type T of value x
 
 
  • 如果将 T 作为 func 方法的返回值类型,即处于协变点,就可以编译通过。
        此时,回到之前的 otherFunc(father) 和 otherFunc(child),child 替换 father 后,child 调用 func 返回 String 类型的对象替换 AnyRef 是合理的。
  1 class B[+T] {
  2   def func(): T = {
  3     null.asInstanceOf[T]
  4   }
  5 }
  6 // 与 Function0[+A] 等价
  • 或者将类型参数 T 改为逆变,
  1 class A[-T] {
  2   def func(x: T) {}
  3 }
  4 // 与 Function1[-A, Unit] 等价
  5 
  6 val father: A[AnyRef] = null.asInstanceOf[A[AnyRef]] // func(x: AnyRef)
  7 val fatherFather: A[Any] = null.asInstanceOf[A[Any]] // func(x: Any)
  8 
  9 def otherFunc(x: A[AnyRef]): Unit = {
 10   x.func(Nil)
 11 }
 12 
 13 otherFunc(father) // success
 14 otherFunc(fatherFather)// success
 

参考网址:

 
by. Memento
posted @ 2018-03-27 09:54  01码匠  阅读(801)  评论(0编辑  收藏  举报