这里介绍一下关于scala中的协变逆变的有关知识,因为真的每次碰见都懵逼的感觉很难受。此处我不会对比Java中的相关协变逆变,只针对scala的进行讲解。
首先我说一下协变,所谓协变,白话文就是说让你的能够使用比原始定义类型的子类。不要懵逼,光看字我本人也看不懂,那么我们来通过实际的例子来讲解一下,首先上代码:
/** * Created by mahuichao on 16/8/4. * 協變處理 */ object CovarTest { class ToList[+T](a: T) { } class Mouse extends Animal {} class Animal {} def main(args: Array[String]): Unit = { // 我們看到,當兩邊是相同的類時,沒有問題 val a: ToList[Mouse] = new ToList[Mouse](new Mouse) val b: ToList[Animal] = new ToList[Animal](new Animal) // 當我們右邊是子類,左邊是父類時,同樣可以 val c: ToList[Animal] = a // 然而當我們左邊是子類,右邊是父類時,卻報錯了 val d: ToList[Mouse] = b } }我们不要关注最外面的object对象,看里面我首先顶一个一个类ToList,我们定义了一个协变类型为T,该类含有参数a的类型也为T。接着我们定义了两个类,一个老鼠Mouse,一个动物Animal,Mouse继承了Animal。接着进入main方法,我们看前两行(注释不算),正常定义了两个对象,没问题。然后看第三行,这句要看清楚,我们左边是Animal类型右边是Mouse类型(不要把你的Java中的继承概念导入进来,会乱的,跟我思路)。那么这里一个问题是我重新定义一个对象c,他是根据a产生的,左边的Animal类型是右边的Mouse类型的父类,协变的定义说明了原始定义指的是Animal,他的子类是Mouse,所以c可以使用。那么反过来看第四行,如果原始是Mouse,但是你后边给的是Animal,这指定会报错的,因为你定义的是协变,是父驱使子。脑子想也能想出来,让老鼠驱动动物们干活,不科学。
那么我们如果理解了协变的过程,就不难理解逆变的过程了,他与协变相反,是使用原始定义的父类类型。那我们直接上代码:
/** * Created by mahuichao on 16/8/4. * 逆變 */ object ContraTest { class ToList[-T](a: T) { } class Mouse extends Animal {} class Animal {} def main(args: Array[String]): Unit = { // 我們看到,當兩邊是相同的類時,沒有問題 val a: ToList[Mouse] = new ToList[Mouse](new Mouse) val b: ToList[Animal] = new ToList[Animal](new Animal) // 然而當我們右邊是子類,左邊是父類時,卻不能通過編譯 val c: ToList[Animal] = a // 當我們左邊是子類,右邊是父類時,可以 val d: ToList[Mouse] = b } }三个类的定义没什么不同,只不过+T换为了-T了。那么我们直接看main方法,直接跳到第三行,转换我们上边的逻辑就很简单了,我们原始是Animal,现在要用Mouse,肯定是不行的。因为逆变定义了我们只能够子驱动父。这里不变就不多讲了,那就是左右一样呗,自己让自己干活。
最后我来加入点东西,就是上界下界的东西。
当我们使用协变或者逆变的时候,我们得好好想想,一个子凭什么去驱动父,这里类型会不会出现越界等问题。其实这个我也想过,内部我没有详细查看,我也不在这里误导大家,我只说说我的想法。我认为之所以能够做到如此灵活是因为设置了界限,试想,我父类很多内容,但是我只让你子类用一部分,其他你用不了,或者说压根让你看不见。这样一来,就不会出现调用上出现越界的情况了,因为人家给你限制了。那么进入主题,首先看下面的代码:
/** * Created by mahuichao on 16/8/4. */ object BoundTest { class ToList[+T](a: T) { // 大家會看到,編譯器會報錯 def quickToList(a: T) = { } // 但是如果返回類型為T,就可以了 def slowToList(): T = { new T } // 如果方法帶參數,那麼需要確定下界,也就是U是T的父類 def ToType[U >: T](u: U): Unit = { println(u) } } // 逆變過程需要確定上界 class ToArray[-T](a: T) { def toType[U <: T](u: U): Unit = { println(u) } } }还是那个类,使用了协变,另外增加了另一个类ToArray,使用了逆变。这里我们详细解说如果我们这些类里面定义了方法,且方法含有参数,并且不返回我们类上定义的类型T,我们需要指定方法中参数的界限。在协变中,我们需要指定方法中含有的参数必须是类定义类型的父类(别问我为什么,我真不知道,我没看源码呢,哈哈)。同理,逆变就是参数是类参数类型的子类。
希望对大家有帮助。感谢开源。