首页 > 编程语言 >java的协变和逆变

java的协变和逆变

时间:2023-04-15 17:33:35浏览次数:35  
标签:java 逆变 List ChildA 协变 new lpa

一、协变和逆变的概念

协变:模板中赋值给A的是A或者A的子类。比如:List<? extends A> listA = List<ChildA>() 即:ChildA 可能是A或者A的子类

逆变:模板中赋值给A的是A或者A的父类。比如:List<? super A> listA = List<ParentA>() 即: ParentA可能是A或者A的父类

二、为何会有协变和逆变

1、关于协变

  咱们在没有模板的时候,把ChildA赋值给A是很正常的情况。

  比如 ChildA ca = new ChildA(); A a = ca; 然后对a 调用各种a中的函数

  但是咱们使用模板进行上述的操作的时候:List<ChildA> lca = new List<ChildA>(); List<A> la = lca; 看起来貌似是可以的,但是编译器却不会通过;因为List<ChildA>和List<A>是两个不同的对象,虽然ChildA是A的子类

  所以就需要协变这个特殊的概念来进行操作

  然后我们则可以A a = la.get(0); a可以操作A中的各个公共方法等,如果有ChildA对应的override函数,则会调用对应的ChildA中的函数

2、关于逆变

  逆变则跟协变相反,在模板中List<ParentA> lpa = new List<ParentA>(); List<? super A> lsa = lpa; 这里限定了赋值给lsa的只能是A或者A的父类;

  咱们这里执行一个操作A a = lsa.get(0); 发现编译不通过,因为get出来的是Object类型。也就是说java这里可以有返回类型,但是类型会是Object

  既然返回的是Object对象,那咱们用原生的List也是可以的, 即:List l = lpa; Object o = l.get(0); 也是能正常取值

  但是java增加了这个逆变的概念是为了为了提高泛型编程的灵活性、类型安全性以及代码的可读性和可维护性。具体说明如下所示

1. 提高代码的灵活性:逆变允许向泛型方法或泛型集合传递更广泛的类型参数。这样可以在泛型代码实现过程中处理更多不同场景的需求,同时确保类型安全。
2. 保证类型安全:使用通配符和逆变,您可以在编译时检查类型。这样做可以确保在运行时不会出现意外的类型转换错误。逆变通过限制泛型参数的范围,使编译器能够在编译期间发现潜在的类型不匹配问题,从而提高代码的类型安全性。
3. 提高代码的可读性和维护性:逆变和协变共同促使程序员编写更通用且具有良好层次结构的代码。这种代码为其他开发者提供了更清晰的约束和边界,有助于提高项目整体的可读性和可维护性。

比如:

        List<ParentA> lpa = new ArrayList<>();
        lpa.add(new ParentA());
        List<? super A> lsa = lpa;
        // 如下两个的add都是报错的,因为都不是A或者A的子类
        // 如果使用原生的List l = lpa; 那么如下两个都是能够正常add
        lsa.add(new ParentA());
        lsa.add(new B());

 

三、协变和逆变的标记记住技巧

协变:? extends T

逆变:? super T

因为协变能够返回T类型,并且对T类型的对象进行操作所以对应的类型肯定是T或者T的子类,咱们java中,使用A extends T 表示A是T的子类;所以协变使用的是? extends T

因为逆变是跟协变相反,协变是T或者T的子类,那么逆变就是T或者T的父类;在java中,要调用父类的函数用的是super,所以逆变使用? super T

咱们在别的地方也有出现类似? extends T的格式,比如T extends B ,但是这两个是不同的概念,T extends B 是限定当前的T模板必须有继承B

 

四、具体的代码

class ParentA {
    void printPA() {System.out.println("ParentA");}
    void print() {System.out.println("print ParentA");}
}

class A extends ParentA {
    void printA() {System.out.println("A");}
    void print() {System.out.println("print A");}
}

class ChildA extends A {
    void printCA() {System.out.println("ChildA");}
    void print() {System.out.println("print ChildA");}
}

class B {}

class ChildB {}

public class HelloWorld {

    public static void processElements(List<? super A> list) {
        for (Object elem : list) {
            // Process the element
        }
    }

    public static void main(String[] args) {

        List<ChildA> lca = new ArrayList<>();
        lca.add(new ChildA());
        List<? extends A> la = lca;
        la.get(0).printA(); // A
        la.get(0).print(); // print ChildA

        List<ParentA> lpa = new ArrayList<>();
        lpa.add(new ParentA());
        List<? super A> lsa = lpa;
        // 如下两个的add都是报错的,因为都不是A或者A的子类
        // 如果使用原生的List l = lpa; 那么如下两个都是能够正常add
//        lsa.add(new ParentA());
//        lsa.add(new B());

        List lo = lpa;

//        lsa.get(0).printA(); // error: Cannot resolve method 'printA' in 'Object'
    }

}

 

标签:java,逆变,List,ChildA,协变,new,lpa
From: https://www.cnblogs.com/czwlinux/p/17321493.html

相关文章

  • java maven-plugin-shade插件 Maven生成的jar运行出现“没有主清单属性”
    命令窗口运行jar,提示“没有主清单属性”  2.1分析问题在打包构建的jar目录内,可以看到有一个MANIFEST.MF文件,如图所示:该文件就是jar运行时要查找的清单目录,其中主清单数据,就是我们要运行的主类(函数入口main所在的类);提示缺少主清单属性,就是文件中少了主清单属性如下所示:正......
  • java——微服务——spring cloud——前言导读
                       黑马课程连接:https://www.bilibili.com/video/BV1LQ4y127n4?p=1&vd_source=79bbd5b76bfd74c2ef1501653cee29d6 ......
  • java——maven——分模块——资源加载属性值
    第一步:   第二步:    第三步:                       ......
  • java——maven——分模块——属性定义与使用
                   版本号统一管理                 ......
  • java——maven——分模块——模块继承
    通过父工程,管理所有子模块的依赖版本管理    把所有依赖放入dependentmanagement下面        所有的子工程需要修改,引入父工程,然后子工程里面的引入依赖的版本号全部去除,交由父工程统一管理:       插件依赖,也可以进行版本统一管理:......
  • java: 无法访问org.springframework.boot.SpringApplication
    在运行springboot项目中的Application.java时出现:错误的类文件: /D:/install/Maven/apache-maven-3.6.1/repository/org/springframework/boot/spring-boot/3.0.5/spring-boot-3.0.5.jar!/org/springframework/boot/SpringApplication.class   类文件具有错误的版本 61.0, ......
  • JavaScript 邮箱 验证正则表达式 ,包看懂
    \w就是[0-9a-zA-Z_]\s是[\t\v\n\r\f]\S是[^\t\v\n\r\f]\W是[^0-9a-zA-Z_]\D就是[^0-9]\d就是[0-9].就是[^\n\r\u2028\u2029]。表示几乎任意字符。varreg=/\w{1,30}(\.\w{1,10}){0,2}@\w{1,10}\.\w{1,10}/g\w{1,30}理解为至少有一个字符,最多30个.\w{1,30}理......
  • java环境搭建
    搭建开发环境下载JDK8下载地址:https://www.oracle.com/cn/java/technologies/downloads/archive/选需要的版本安装即可配置环境变量:此电脑-->鼠标右键-->属性-->高级系统设置-->环境变量-->新建-->确定-->打开Path,添加bin和jreJAVA_HOME:java安装根目录bin:%JAV......
  • Java MyBatis-Plus(4)MybatisPlus整合Pagehelper实现分页
    序言 /***pageInfo对象中属性含义*privateintpageNum;//当前页码*privateintpageSize;//设置每页多少条数据*privateintsize;//当前页有多少条数据*privateintstartRow;//当前页码第一条数据的*privateintendRow;//......
  • 前端基础之JavaScript
    目录JS简介JS基础变量与常量基本数据类型数值(number)字符串的常用方法对象的常用方法forEach()splice()map()运算符流程控制函数内置对象JS简介全称JavaScript但是与Java一毛钱关系都没有之所以这么叫是为了蹭Java的热度它是一门前端工程师的编程语言但是它本身有很多逻辑错误(......