Scala协变逆变上界下界

这里介绍一下关于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,我们需要指定方法中参数的界限。在协变中,我们需要指定方法中含有的参数必须是类定义类型的父类(别问我为什么,我真不知道,我没看源码呢,哈哈)。同理,逆变就是参数是类参数类型的子类。

希望对大家有帮助。感谢开源。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值