软构学习心得:协变与逆变

java在继承派生的过程中,有一个重要的原则LSP(Liskov Substitution Principal),里氏替换原则。

1.子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法;
2.子类中可以增加自己特有的方法。
具体表现在:
• 子类必须实现父类所有非私有的属性和方法,或子类的所有非私有属性和方法必须在父类中声明。即,子类可以有自己的“个性”,这也就是说,里氏代换原则可以正着用,不能反着用(在项目中,采用里氏替换原则时,尽量避免子类的“个性”,一旦子类有“个性”,这个子类和父类之间的关系就很难调和了)。根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。
• 尽量把父类设计为抽象类或者接口。让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。
 

子类型需要实现抽象类型中所有未实现的方法;

子类型可以增加方法,但不可以删除;

子类型中重写覆盖(Override)父类中的方法时,方法的形参必须完全一致;

子类型中重写覆盖(Override)父类中的方法时,方法的返回值必须相同或更为严格(协变);

子类型中重写覆盖(Override)父类中的方法时,不能抛出额外的异常(协变);

子类型的规约应不变或更强;

对于协变(Covariance):

我个人的通俗化理解就是,变得更为“狭窄”了。即父类到子类变得越来越具体(或者不变)。父亲生的儿子,受父亲管教,条条框框比较多,更为“具体”。看下面几个例子理解协变:

数组是可协变的:

Number[] nums = new Number[2];
nums[0] = new Integer(1);
nums[1] = new Double(6.16);
System.out.println(nums[0]);
System.out.println(nums[1]);

该代码可以正常编译运行输出。因为nums是Number的数组,Integer与Double均为Number的子类,这属于对象类型的协变,type of an object

再看下述代码:

Integer[] mi = {1,2,3,4};
Number[] mn = mi;
mn[0] = 3.14; // run-time error

系统会抛出 ArrayStoreException的异常,数组元素对象类型不匹配。为什么呢?因为mn是引用类型type of a reference,其底层持有对象为Integer,无法将3.14小数赋值给Integer。但第2行代码实现的协变是没有错误的,如果第3行代码赋的值改为整型,则该代码可以正常编译运行。

再看下述代码:(泛型不是协变的

List<Integer> listint = new ArrayList<>();
List<Number> listnum = listint; // compiler error 

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
Type mismatch: cannot convert from List<Integer> to List<Number> 

因为本质上,listint与listnum均为List,本身是“不变”的。可以看作是两个装有物品的桶,内部装有的物品有继承关系,但桶本质上是相同的。A与B有关系,但Box<A>与Box<B>没有该关系。

看一下更为底层的原因:泛型类型中的参数在编译时会经历“类型擦除(type erasure)”的过程——编译代码完成后,编译器丢弃类型参数的类型信息,如下图所示:

所以不支持从List<Number>到List<Integer>的协变。

对于逆变(Contravariance),反协变:

我个人的通俗化理解就是,变得更为“宽泛”了。即父类到子类变得越来越具体(或者不变)。参数类型是与协变相反的变化,不变或更加抽象。事实上逆变在java中是不被允许的,但会当作Overload看待。

泛型中的通配符(Wildcards in Generics):

<? super E> //下限通配符 指允许接受E类或其父类
<? extends E> //上线通配符 指允许接受E类或其子类

看下述代码便可以很好理解了: 

List<? extends Number> list = new ArrayList<Integer>();
List<? super Number> list = new ArrayList<Number>();

 参考文章:

https://blog.csdn.net/denglie6810/article/details/101489852?utm_source=app&app_version=4.10.0

以上仅是本人个人学习的过程体会总结,如有错误,欢迎及时指出探讨!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值