Scala语言备忘拾遗 – 2 型变
Scala语言备忘拾遗 – 2 型变
Scala中型变是要指明泛型类中类型参数的父子关系和该泛型类的父子关系之间的关系.
写完这句话,深感不安.感觉越说越乱.为了理清各种关系,将上面这句话分解成:
- 泛型类中类型参数
- 类型参数的父子关系,假设该关系为 A
- 泛型类的父子关系,假设该关系为 B
下面围绕上述三点展开说明
协变
举个例子, List的定义是这样的 List[+A], 这里A是类型参数
,+
表示型变的一种:协变
.
比如有类型List[Animal]
和List[Cat]
,因为+A表示协变,而 Cat是Animal的子类(关系A),因此List[Cat]
也是List[Animal]
的子类(关系B).
假如一个方法接受List[Animal]
作为参数,那么调用是传递List[Cat]
作为参数也是可以的.
上述关系A和B是协调的,符合直觉的,所以叫协变
.
逆变 和 不变
逆变
和协变正好相反.比如有类型Printer[-A]
且Y是X的子类,则Printer[X]
是Printer[Y]
的子类.
不变
就是上述关系A和B没有相关性.比如Container[A],Container[X]
和Container[Y]
没什么相关的关系.
另一个例子, 理解类型 trait Function1[-T, +R]
另一个可以帮助理解型变的例子是 Scala 标准库中的 trait Function1[-T, +R]。参考 这里
Function1 表示具有一个参数的函数,其中第一个类型参数 T 表示参数类型,第二个类型参数 R 表示返回类型。
Function1 在其参数类型上是逆变的,并且在其返回类型上是协变的。
为什么
参数是逆变
的,而返回值是协变
的呢?
假如 你有一个函数myFunc
接受一个Function1[-B,+X]
类型的参数f,这个f其实是一个函数类型的变量,你在方法myFunc
中是是要调用这个f的,调用方式为
x = f(B)
然后 返回值X类型的x也会被用作某个函数的参数.
按照协变和逆变的规定, 类型为Function1[A,Y]
的函数g,是f的子类,也可以传给你myFunc的,其中A是B的父类,Y是X的子类.
这样调用时,myFunc中f(B)实际上变成了g(B),注意,按照类型规定g:Function1[A,Y]
接受的A类型的参数(期望的调用方式为g(A)),而在myFunc中,因为f是Function1[B,X]类型,
所以传给f的是B类型的参数,是A的子类,于是myFunc中的调用g(B)不会报错,因为B是A的子类,接受A的地方必然也接受B.
在myFunc
中某个地方,用到f(B)的返回值,类型为X,当用g替换了f时,返回值类型实际上是Y,Y是X的子类,能用X的地方是用子类Y自然也是没问题的.
微信赞赏 支付宝赞赏