因为Function的泛型里定义了函数入参和出参分别是“逆变”和“协变”的:
[scala]trait Function1[-T1, +R] {…}[/scala]
所以A=>B
这样的函数类型,也可以有继承关系的。
我们做个测试,先简单些,只看出参类型的(协变容易理解些),A=>B
和A=>C
两个函数类型;
如果C extends B
则A=>C
是A=>B
的子类型
[scala]scala> class A; class B; class C extends B
//定义A=>C类型的函数
scala> val t2 = (p:A)=>new C
//可以把 A=>C类型的函数赋值给 A=>B类型的
scala> val t3:A=>B = t2
//或直接把t2造型为 A=>B
scala> t2.asInstanceOf[A=>B]
[/scala]
再看看入参类型,这个是逆变,继承关系正好相反。
假设: X=>R
,Y=>R
如果 Y extends X
则 X=>R
是 Y=>R
的子类型
[scala]scala> class R; class X; class Y extends X
//定义X=>R类型的函数
scala> val f1 = (x:X)=>new R
//把X=>R类型的函数赋值给 Y=>R 类型的
scala> val f2:Y=>R = f1
//或直接造型
scala> f1.asInstanceOf[Y=>R]
[/scala]
协变和逆变的场景与java泛型的”PECS原则”一致
注: PECS 是Joshua Bloch在《Effictive Java》里提出的一个原则。
当参数(容器)是一个生产者(producer)提供元素给你的代码来用(即容器只读),那么容器的泛型应该使用:
Collection< ? extends T >
当参数(容器)作为一个消费者(consumer)来消费你提供的数据(即容器可写),那么容器的泛型应该使用:
Collection< ? super T >
Pingback: Scala Convariance and Contravariance(逆变与协变) | 小白菜