Scala:协变点和逆变点

以下内容是我在看了hongjiang博文之后的一些体会,做个记录,感谢hongjiang

先给一个示例,定义一个类A,其类型参数是协变的:

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

上面的代码通不过编译,报错如下:

covariant type T occurs in contravariant position in type T of value x

要解释这个问题,需要理解协变点和逆变点的概念。我们可以考虑这样一种情况来解释程序为什么报错,既然A的类型参数T是协变的,那么A[AnyRef]是A[String]的父类,A[AnyRef]对应的funcfunc(AnyRef)A[String]对应的funcfunc(String),我们定义father是一个A[AnyRef]实例,childA[String]实例。当我定义了另一个函数gg的参数为A[AnyRef],因此g(father)当然是没有问题的,又因为childfather的子类,因此按理来说g(child)也是没有问题的,但是fatherfunc可以接受AnyRef类型的参数,而childfunc只能接受String类型的参数。因此,如果编译器允许用child替换father,那么替换后g中的参数调用func就只能传入String类型的参数了,相当于g的处理范围缩小了。所以编译器不允许这种情况,因此会报错。反过来一想,如果传入的是father的父类,那么g的处理范围就变大了,所有适用于father的情况都适用于father的父类,因此,如果把A的类型参数T声明为逆变的,就不会有问题了。

  class A[-T] {
    def func(x: T) {}
  }

总结:传入A的类型参数会作为A中方法的参数的类型(如果有参数的话),我们知道一个方法中如果有类型为X的参数,那么这个方法可以接受类型为X的子类的参数。同理上面的情况,func原来可以接受类型为AnyRefAnyRef的子类作为参数,但是如果一协变,那么func就只能接受类型为StringString的子类作为参数,作用范围减小了。
图片描述

因此方法的参数的位置被称为逆变点A中的类型参数声明为协变,因此编译时出错,声明为协变则没有问题。
而方法返回值的位置被称为协变点,同样考虑上面的情况:

  class A[+T] {
    def func(): T = {
      null.asInstanceOf[T]
    }
  }

也是考虑father:A[AnyRef]child:A[String],当用child替换father后,childfunc方法会返回String类型的对象来替换AnyRef,因此是合理的。

阅读 4.1k

推荐阅读
tinylcy
用户专栏

闷声搬大砖

9 人关注
28 篇文章
专栏主页