如何理解Java中的协变

前言:

引用C++中的一段, 在C++中,只要原来的返回类型是指向类的指针或引用,
新的返回类型是指向派生类的指针或引用,覆盖的方法就可以改变返回类型 。这样的类型称为协变返回类型(Covariant returns
type).

关于协变:
协变和逆变维基上写的很复杂,但是总结起来原理其实就一个。

  • 子类型可以隐性的转换为父类型说个最容易理解的例子,int和float两个类型的关系可以写成下面这样。int ≦ float :也就是说int是float的子类型。按照上面的原理来说,就是int可以转换成float类型,比如int 3可以默认转化为float 3.0,但是float 3.14默认转换成int 3 感觉总是怪怪的。所以我们说3可以是float类型,但是3.14不能是int类型。现在开始说协变和逆变。维基百科的解释是:

协变(covariant),如果它保持了子类型序关系≦。该序关系是:子类型≦基类型。

在面向对象程序设计中,协变返回类型指的是子类中的成员函数的返回值类型不必严格等同于父类中被重写的成员函数的返回值类型,而可以是更"狭窄"的类型.
Java 5.0添加了对协变返回类型的支持,即子类覆盖(即重写)基类方法时,返回的类型可以是基类方法返回类型的子类。协变返回类型允许返回更为具体的类型。

在java代码中,人们惯性的认为一个方法中只能返回一种返回值或者无返回。博主在做开发过程中碰到了这样一种情况,安卓客户端请求数据,后台可能返回两种结果(1)访问令牌失效,无数据返回。(2)正常获取数据。

这样的情况下需要根据访问令牌标识来判断是否有数据返回。当无效时返回用户重新登录提示,正常时则返回数据。显然,返回的结果有两种,那么一个方法里面只能返回一种类型的禁锢使得开发起来略显笨拙。 使得开发起来相当难受。
因为java里面有没有指针,所以无法做到以上的功能。但是派生类是包含父类的共有方法和保护方法的。再根据里氏代换原则(任何基类可以出现的地方,子类一定可以出现),那么凡是派生类包含的基类属性,基类一定包含,这样的话情况会好很多。 那么只要由派生类设置属性返回到基类中,基类方法一定能操作这些属性 。那么一条完整的可变返回类型就可以建立起来。下面结合代码来验证:

class Person {
 2     /** 假设每个人都有一个名字 **/
 3     String name;
 4 
 5     public String getName() {
 6         return name;
 7     }
 8 
 9     public void setName(String name) {
10         this.name = name;
11     }
12 
13 }// person
14 
15 class SuperMan extends Person {
16     /** 假设每个超人都有身高 **/
17     int height;
18 
19     public int getHeight() {
20         return height;
21     }
22 
23     public void setHeight(int height) {
24         this.height = height;
25     }
26 }// SuperMan

SuperMan类继承Person类(博主当时想不出来什么好东西了- -!),则SuperMan类包含name和height两个属性,基类Person只包含name属性。

先说明一下普通的操作方式,以此作为对比。代码如下:

public static Person getPerson1() {
2         Person person = new Person();
3         person.setName("无语");
4         return person;
5     }
1 public static SuperMan getSuperMan1() {
2         SuperMan man = new SuperMan();
3         man.setName("不知道");
4         man.setHeight(120);
5         return man;
6     }

很简单,返回值就是预期的返回类型,但是如果把返回类型替换一下结果如何。如下所示:

1 public static SuperMan getSuperMan() {
2         Person person = new SuperMan();
3         person.setName("小虎");
4         return (SuperMan) person;
5     }

以上只能得到Superman的name属性无法得到height属性,如果在返回之前强转以此加上height属性后返回即可得到一个完整的SuperMan对象。这里不再尝试,读者自行处理。

1 public static Person getPerson() {
2         SuperMan man = new SuperMan();
3         man.setHeight(100);
4         man.setName("大明");
5         return man;
6     }

以上代码就是此文章的核心,也是解决文章开头问题的核心。怎么操作呢?

我们写两个类,ErroMessage和DataMessage类,其中ErroMessage是基类,DataMessage继承基类。ErroMessage包含erroMessage属性类型为private,DataMessage包含data属性。那么ErroMessage就只包含erroMessage属性,操作时只会引起erroMessage的变化,而不会引起data的变化。而操作DataMessage类时只能操作data属性。而在一个方法中,只要保持方法类型为ErroMessage,返回时就可以根据需要来返回相应的类型。这样使用方法时, 用派生类得到所有属性,如果子类属性为空,那么基类属性一定不为空。如果派生类属性不为空,那么基类属性一定为空。 这样就可以按需取得相应的数据。操作起来相当简单。代码操作如下:

1 printDivider("person");
2         Person person = getPerson();
3         System.out.println("person的名字是" + person.getName());
4         SuperMan superMan = (SuperMan) person;
5         System.out.println("通过强转获取到的超人高度是这样的" + superMan.getHeight());

总结来讲就是: 派生类可以包含基类的共有属性,那么基类一定能从派生类获取到自身暴露给派生类属性的值 。

数组协变:
Student extends Teacher

		Teacher[] teacher = new Teacher[2];
		Student[] student = new Student[2];
		System.out.println(student instanceof Teacher[]);
返回值:true
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值