协变与逆变

本文探讨了Java和Scala中的型变概念,包括协变和逆变。在Java中,协变体现在从泛型类取数据时,逆变则在往泛型类写数据时。而在Scala中,协变体现在返回值类型,逆变体现在形参类型。文章解释了为何需要协变和逆变,并举例说明了Scala中型变的优秀实现。同时,提出了确定型变点的方法。
摘要由CSDN通过智能技术生成

型变(协变与逆变)

今天理清了一下关于java与scala中的型变这个概念, 比较一下这两个JVM语言是如何对待这个问题的

Java中的协变与逆变

static class Person {
  public void hi() {
    System.out.println("hi");
  }
}
static class Student extends Person {
  public void hello() {
    System.out.println("hello");
  }
}

数组中的协变

Student[] students = new Student[2];
Person[] peoples = students;//发生协变
peoples[0] = new Student();
peoples[1] = new Person();//运行异常 ArrayStoreException

使用通配符处理协变逆变

    //首先强调一下这个编译异常,泛型是不支持型变,如下会出现type不相容
    //List<Person> list3 = new ArrayList<Student>();//incompatible error
    //逆变
    List<? super Student> list = new ArrayList<Person>();
    list.add(new Student());  
    //list.get(0).hi(); compile error

    //协变
    List<? extends Person> list2 = new ArrayList<Student>(){{
      add(new Student());
    }};
    //list2.get(0).hello();//compile error
    list2.get(0).hi();

从上面例子可以看出来: 方法的形参是逆变的、返回值是协变的

  • 要从泛型类取数据时,用extends, 发生协变
  • 要往泛型类写数据时,用super, 发生逆变
  • 既要取又要写,就不用通配符

我们可以看出java在这一方面做不是很好,在看看scala中的使用

scala中的协变与逆变

  class Person {
    def hi(): Unit = {
      println("hi person")
    }
    override def toString: String = "Person\n"
  }

  class Student extends Person {
    def hello(): Unit = {
      println("hello")
    }
    override def hi(): Unit = {
      println("hi student")
    }
    override def toString: String = "Student\n"
  }

协变点在返回值,PersonStudent的父类,则Function1[Student, Person]Function1[Student, Student]的父类,这就是协变

  // Person> Student
  //Function1[Student, Person] > Function1[Student, Student]
  val fun = new Function1[Student, Student] {
    def apply(x: Student): Student = {
      //return 发生协变, 实际return 更具体
      x
    }
  }

  def test(fun: Function1[Student, Person], stu: Student) {
    print(fun.apply(stu)) //Student
  }
  test(fun, new Student)
  //简写如下:
  def test2(fun: Student => Person, stu: Student) {
    print(fun(stu))
  }
  test2(x => x, new Student)

逆变点在形参,PersonStudent的父类,而Function1[Student, Person]Function1[Person, Person]的父类,这就是逆变

  //Function1[Student, Person] > Function1[Person, Person]
  val fun1 = new Function1[Person, Person] {
    def apply(x: Person): Person = { //x 发生逆变, 实际传入只会更具体
      x
    }
  }

  def test1(fun: Function1[Student, Person], stu: Student) {
    print(fun.apply(stu))//Student
  }
//  def test1(fun: Student => Person, stu: Student) {
//    print(fun(stu))
//  }

  test1(fun1, new Student)

Function1[-T1,+R]是scala中的trait,-表示逆变,+表示协变,可以看出scala在这方面做得非常优秀

为什么需要协变与逆变?

里氏代换原则: 任何使用基类的地方,都能够使用子类替换,而且在替换子类后,系统能够正常工作,这里就是要遵循这个原则,协变比较容易理解,逆变有点拗,Function1[Student, Person]是父类,Function1[Person, Person]的子类,所有父类都可以被子类替代,如方法:test1(fun: Function1[Student, Person], stu: Student)或者test1(fun: Function1[Teacher, Person], t: Teacher),巧妙的泛化了参数,使方法更抽象,减少重复代码

scala中型变点

如何确定型变点,笼统的说方法的形参是逆变的、返回值是协变的,但是如果形参中是一个方法呢?看下面的例子的注释:

  trait Fun[+T1, -T2, +R] {
    def apply(fun: Function1[T1, T2], param: T2): R
  }

  val f = new Fun[Person, Student, Student] {
    //  协变点确定
    //(         -         )  =>  (+) default
    //(      -        , - )  =>  (+)
    //(-( (-) => (+) ), - )  =>  (+)
    //(   (+) => (-)  , - )  =>  (+)
    override def apply(fun: Person => Student, stu: Student): Student = {
      println("fun1")
      fun(stu)
    }
  }

  println("fun return => " + f((stu: Person) => {
    println("fun2")
    stu.hi()
    stu.asInstanceOf[Student]
  }, new Student))

执行结果

fun1
fun2
hi student
fun return => Student

参考

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值