首先定义一个类 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 类的定义违反了里氏替换原则,所以编译器无法通过。
反之,如果用 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