写在前面
- 和小伙伴分享一些 java 小知识点,主要围绕下面几点:
- 什么是 逆变(contravariant) & 协变(covariant) ?
- 数组 支持 协变&逆变 吗?
- 泛型 支持 协变&逆变 吗?
- 部分内容参考 《编写高质量代码(改善Java程序的151个建议)》
- 博文理解有误的地方小伙伴留言私信一起讨论
生活不能等待别人来安排,要自己去争取和奋斗;而不论其结果是喜是悲,但可以慰藉的是,你总不枉在这世界上活了一场。有了这样的认识,你就会珍重生活,而不会玩世不恭;同时,也会给人自身注入一种强大的内在力量。 —— 路遥 《 平凡的世界 》
关于 协变 与 逆变 到底是什么意思,其实很好理解,用一句话描述:(小伙伴们看到下面的话,会不会想到这不就是 多态 吗,哈,今天我们只看 协变和逆变 ,关于 多态 的一些内容,如 强制多态 , 包含多态 , 重载多态 等之后有机会和小伙伴们分享)
协变 即指 窄类型替换宽类型 , 逆变 即 宽类型覆盖窄类型
这里的 窄类型 即 子类(派生类) ,这里的 宽类型 即 父类(基类,超类) ,那这里的 替换覆盖 又是什么意思,这里就要说到 OO(面相对象)六大设计 原则之一的 LSP(里氏代换原则 Liskov Substitution Principle) , 里氏代换原则 中说,任何 基类 可以出现的地方, 子类 一定可以出现。 LSP 是继承复用的基石,只有当 派生类 可以替换掉 基类 ,且软件单位的功能不受到影响时, 基类 才能真正被复用,而 派生类 也能够在 基类 的基础上增加 新的行为 。
我们来看一段代码
package com.liruilong;
import java.util.Arrays;
/**
* @Project_name: workspack
* @Package: com.liruilong
* @Description:
* @Author: 1224965096@qq.com
* @WeChat_Official_Accounts: 山河已无恙
* @blog:
* @Date: 2022/2/11 1:18
*/public class CovariantDemo {
public static void main(String[] args) {
NUmber [] numbers = {1,1L,3d,2.0F};
Arrays.stream(numbers).forEach(System.out::print);
}
}
Number类是所有基本类型封装类的父类,同理基本类型封装类为Number类的子类,关于自动装箱和自动拆箱是java在JDK1.5的时候引入的新特性,我们这里不多讲,上面的代码可以正常编译,并且输出下面的内容,这里,数组里的基本类型装箱为封装类放到了堆中,这些封装类可以出现在Number类定义的数组中,说明子类可以替换了父类,即数组是满足协变的。
113.02.0
Process finished with exit code 0
既然数组支持 协变 ,那么 逆变 呢?我们来看看
package com.liruilong;
import java.util.Arrays;
/**
* @Project_name: workspack
* @Package: com.liruilong
* @Description:
* @Author: 1224965096@qq.com
* @WeChat_Official_Accounts: 山河已无恙
* @blog:
* @Date: 2022/2/11 1:18
*/public class CovariantDemo {
public static void main(String[] args) {
Number [] numbers = {new Object()};
Arrays.stream(numbers).forEach(System.out::print);
}
}
这里我们把数组元素换成 Object类 ,即所有类的父类,希望是可以通过 父类来覆盖代替子类 ,但是 直接编译报错 ,说明 数组 是 不支持直接逆变 的
Error:(17, 30) java: 不兼容的类型: java.lang .Object无法转换为java.lang.Number
数组不支持直接逆变,那么是否可以接见的实现逆变的,这里我么就要用到 多态 里的一种, 强制多态 ,即 强制类型转化 试试
package com.liruilong;
import java.util.Arrays;
/**
* @Project_name: workspack
* @Package: com.liruilong
* @Description:
* @Author: 1224965096@qq.com
* @WeChat_Official_Accounts: 山河已无恙
* @blog:
* @Date: 2022/2/11 1:18
*/public class CovariantDemo {
class A {
}
class B extends A{
}
public static void main(String[] args) {
A a = new CovariantDemo().new A();
B [] bs = {(B) a};
Arrays.stream(bs).forEach(System.out::print);
}
}
类型转化报错 。说明对于 数组的逆变 来讲,是 不支持逆变 的,将父类强制转化为子类报类型转化异常,java并没有对这方面做限制。
Exception in thread "main" java.lang.ClassCastException: com.liruilong.CovariantDemo$A cannot be cast to com.liruilong.CovariantDemo$B
at com.liruilong.CovariantDemo.main(CovariantDemo.java:24)
Process finished with exit code 1
通过上面代码,我们可以知道数组支持协变,不支持逆变 ,那泛型呢?对于协变和逆变是否支持
泛型不支持协变也不支持逆变 ,即不能把一个 父类对象赋值给一个子类类型变量 ,相反也是同理。
下面我们看看代码
package com.liruilong;
import java.util.ArrayList;
import java.util.List;
/**
* @Project_name: workspack
* @Package: com.liruilong
* @Description:
* @Author: 1224965096@qq.com
* @WeChat_Official_Accounts: 山河已无恙
* @blog:
* @Date: 2022/2/11 1:18
*/public class CovariantDemo {
public static void main(String[] args) {
List<Number> ln = new ArrayList<Integer>();
}
}
java 为了保证 运行期 的 安全性 ,必须保证 泛型参数类型 是 固定 的,所以它 不允许 一个 泛型参数 可以同时包含 两种类型 ,即使为 父子关系 也不行。所以直接 编译报错 ,即 泛型不支持协变也不支持逆变 .
Error:(17, 27) java: 不兼容的类型: java.util.ArrayList<java.lang.Integer>无法转换为java.util.List<java.lang.Number>
但可以使用 通配符 (Wildcard)模拟协变逆变 ,通配符在编译期有效,在运行期必须为一个明确的类型
package com.liruilong;
import java.util.ArrayList;
import java.util.List;
/**
* @Project_name: workspack
* @Package: com.liruilong
* @Description:
* @Author: 1224965096@qq.com
* @WeChat_Official_Accounts: 山河已无恙
* @blog:
* @Date: 2022/2/11 1:18
*/public class CovariantDemo {
public static void main(String[] args) {
List< ? extends Number > list = new ArrayList<Integer>();
}
}
即 Number 的子类型都可以为泛型类型参数,即允许 NUmber 所有的子类作为泛型参数类型,在运行期为一个具体的值. 编译没有报错
Process finished with exit code 0
逆变同样也是可以,即泛型可以通过 super 和 extends 来模拟实现协变和逆变,但是 本身是不存在协变和逆变 的,这里主要利用了泛型在编译器有效
List< ? super Integer> li = new ArrayList<Number>();
关于协变逆变就和小伙伴分享到这里,嗯,还有协变逆变方法,这里要简单说明下
协变方法 : 即子类的方法返回值的类型比父类方法要窄,即该方法为协变方法,也称 多态,覆写,重写 。
//子类的 do Stuff()方法返回值的类型比父类方法要窄,即该方法为协变方法,也称多态。
class A{
public Number doStuff(){
return 0;
}
}
class B extends A{
@Override
public Integer doStuff(){
return 0;
}
}
逆变方法 :子类的方法返回值的类型比父类方法宽,此时为逆变方法。虽然子类扩大了父类的输入返回参数,但是这里已经是 重载 了。
//子类的doSutff方法返回值的类型比父类方法宽,此时为逆变方法,
class C {
public Integer doStuff(Integer i) {
return 0;
}
}
class D extends C {
public Number doStuff(Number i) {
return 0;
}
}