您的位置 首页 java

[JAVA冷知识]什么是逆变与协变?数组是否支持协变&逆变?泛型呢?

写在前面


  • 和小伙伴分享一些 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;
        }
    }
  

文章来源:智云一二三科技

文章标题:[JAVA冷知识]什么是逆变与协变?数组是否支持协变&逆变?泛型呢?

文章地址:https://www.zhihuclub.com/200838.shtml

关于作者: 智云科技

热门文章

网站地图