一文了解PECS

简介: 为什么要写这篇文章呢,是因为最近在看阿里巴巴Java开发手册时候,注意到这一条规范:【强制】泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方 法,而<? super T>不能使用 get 方法,做为接口调用赋值时易出错。 说明:扩展说一下 PECS(Producer Extends Consumer Super)原则:第一、频繁往外读取内 容的,适合用<? extends T>。第二、经常往里插入的,适合用<? super T>。以下,将对PECS和这条规范进行解读。

一、什么是泛型通配符


1、简单定义泛型统配符


1.在了解泛型的统配符之前,我们先了解下什么是泛型,泛型是一种包含类型参数的类,值得注意的一点是这里的类型必须是引用数据类型,而且放在尖括号< >内,这里引进了类型参数,将类直接作为了参数。


2.那么是什么泛型统配符呢,我没有找到定义,所以我自己给它下了个定义。泛型通配符是在泛型的使用中,用来表示对泛型类型进行类型范围限定的特殊符号。这里用通配符就是为了表明要输入的类型要在一定范围之内,说的通俗一些其实就是一个类型取值范围,而最大值是Object这是确定的。


2、泛型通配符的分类


  1. <?>:无限通配符,可以在?中放入里放入任何引用数据类型,用来接收任意引用数据类型。
  2. <? extends E>:这个表明的是通配符的上界,通俗讲就如同取值范围中的负无穷到E,即小于等于E的所有类型, 因为E是最大的类型(最大可以达到Object),表明可以输入所有的E的子类和E,等下会进行细致的讲解。
  3. <? super E>:这个表明的是通配符的下界,通俗讲其取值范围就是E到最大值Object(因为Object是所有类的基类),就是大于等于E,小于等于Object类。


注意:这里能制定上界或者下界,但是不能同时制定,然后<? extends E>中的extends不一定表示类与类的继承还可以表示实现的关系,然后通配符一般是用在方法的形参声明和方法调用上,无法用于定义类和接口中。


二、泛型通配符讲解


1、通配符的使用


1.无限通配符<?>的使用:可以传入任何引用数据类型

A 在调用方法时使用?通配符的过程中无法使用add方法。


原因分析:因为通配符?代表任意的数据类型,但是当我们调用的时候或者用在方法的声明上,其实这个时候还是没有给?通配符一个指定的引用数据类型,那么Java出于安全起见,就不可能允许添加元素。


B 以上的add方法虽然无法调用,add是例外。


原因分析:因为null可以给任意引用数据类型赋值,代表任意引用数据,但是很容易引起NullPointerException。


C 注意使用List<?>和List当作形参时的作用不能等同,比如当传入List时List<?>可以接收,但是List无法接收。


原因分析:因为?代表任何参数类型可以接收,但是List中虽然Object是所有子类的父类,但是List不是List的父类,List是ArrayList等类的父类,这就是为什么泛型前后要一致的原因,从数组的角度去理解集合就比如Object[ ] arr不是Integer[ ] arr1的父类。


2.上界通配符<? extends E>的使用:可以传入E和E的子类


A <? extends E>作为形参时例如List<? extends E>可以使用集合的get方法来获取E或者E类型本身的元素。


原因分析:当我们用get方法时我们其实是在获取集合里内部的元素,但是我们的集合的数据类型还没有确定,但是我们可以获得一些明确的已知条件,那就是在<? extends E>中最大的类型是E,而且这个E最大是Object,所以我们可以利用这一点,那么我们就可以清楚地了解到该集合里面的获取的元素肯定是E或者Object的子类,他们的范围肯定小于E或者Object,那么我们就可以用Object和E这两个范围比集合里面的元素大的类去接收集合里面的元素。(注:可能略显啰嗦但是我就是想解释清楚。)


B 在使用上界通配符时,无法调用add方法来添加非null的元素。


原因分析:由于上面已经说得很清楚了,<? extends E>作为形参时例如List<? extends E>这时最大类型是E和Object,但是我们不清楚最小的类型是什么,因为此时?这个通配符没有被赋值,我们调用add方法是要添加集合元素或者集合元素的子类,但是我们没法明确肯定该集合元素类型,或者比该集合元素范围更小的子类,那么Java就不会允许添加元素。


3.下界通配符<? super E>的使用:可以传入E或者E的父类


A 在使用下界通配符时,无法使用get方法获取Object以外的元素,或者需要向下转型,但是可能出现ClassCastException的异常。


原因分析:上界通配符,在使用get方法的时候,此时类型没有明确还是问号?我们只能明确其最大父类或者接口时,我们才能接收,但是我们只能明白<? super E>作为形参时例如List<? super E>时,只能明确Object是最大父类,其他的一概不知,所以只能Object o = list.get(0)。


B 可以使用集合的add方法添加E或者E的子类。


原因分析:上界通配符已经解释很清楚了,add方法添加元素时,?类型不确定就要明确该?类型的最小子类,只要比可能存在的最小子类或者子接口小的任意引用数据类型的对象,我们都可以将其添加,而下界通配符<? super E>当作形参时例如List<? super E>,此时E就是最小子类,此时add方法可以添加E或者E的父类或者接口。


2、对PECS原则的解读


1.什么是PECS原则?


PECS是Producer Extends Consumer Super的递归缩写,是Java中使用泛型通配符的原则。


2.里巴巴的通配符使用规约


泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方法,而 < ? super T> 不能使用 get 方法,做为接口调用赋值时易出错。


说明:扩展说一下 PECS 原则:第一、频繁往外读取内容的,适合用<? extends E>。第二、经常往里插入的,适合用<? super E> 。


3.对PECS原则的简单解读


字面意思是生产者要被继承要被当作上界通配符<? extends E>的上界E,消费者要继承其他类要被当成下界通配符<? super E>的下界E,再借助下阿里巴巴的泛型开发规约去理解下,应该就是当这个被传入的类型需要进行很多get操作获取数据的话,那么请使用上界通配符这时这个上界就如同生产者一样,因为它能被不断get到,而当需要不断进行add方法添加数据的话,请使用下界通配符这时这个下界就如同消费者一样,因为它不断地索取,因为我们在不断地add元素给它。


三、代码演示


import java.util.*;
/**
 * @author 步尔斯特
 * @date 2022-01-25 上午11:09
 **/
public class ListTest {
    public static void main(String[] args) {
        //最小子类
        Student student = new Student();
        // 多态体现
        List<Animal> animals = new ArrayList<>();
        animals.add(student);
        List<Person> person = new ArrayList<>();
        person.add(student);
        List<Student> students = new ArrayList<>();
        students.add(student);
        // List<?> 无界通配符
        // List<? extends Person> 上界通配符  ?表示Person的子类
        // List<? super Person>   下界通配符  ?表示Person的父类
        // 调用上界通配符接口保存信息,接口入参为 List<? extends Person>,只能传入List<Person>或者List<Student>
        saveExtendsList(person);
        saveExtendsList(students);
//        saveExtendsList(animals);//不满足,编译器报错
        // 调用下界通配符接口保存信息,接口入参为 List<? super Person>,只能传入List<Person>或者List<animals>
        saveSuperList(person);
        saveSuperList(animals);
//        saveSuperList(students);//不满足,编译器报错
        // 调用别人提供的接口,获取集合信息,不清楚提供方到底返回的是哪个集合,List<Person> 还是 List<Student> ?
        List<? extends Person> extendList = getExtends();
        // 调用get会返回上边界父类对象,可以用父类方法操作子类
        Person p = extendList.get(0);
//        Student s = extendList.get(0);//不满足,编译器报错,编译器表示,老色龙后代五花八样,谁知道你是啥样的人
        // 下面三行全报错,按理说应该可以添加student对象,从当前类看只有animal,student,person三个类,如果还有别的呢,
        // 比如学生下面还有两个同级子类,高中生和小学生,别人提供的接口返回的是高中生,然后添加了小学生,编译器表示这个我懵逼,我得报错
//        extendList.add(new Student());//不满足,编译器报错
//        extendList.add(new Person());//不满足,编译器报错
//        extendList.add(new Animal());//不满足,编译器报错
        // 那有没有啥办法让编译器不懵逼呢?
        // 如果您很确定返回的是某个类的数据,强转
        List<Student> studentList = (List<Student>) getExtends();
        studentList.add(new Student());
        List<Person> personList = (List<Person>) getExtends();
        personList.add(new Student());
        List<Animal> animalList = (List<Animal>) getExtends();
        animalList.add(new Student());
        // 若果您看懂了上面,下面soEasy了
        // 调用别人接口获取数据,只知道数据是person或者person父类的集合
        List<? super Person> aSuper = getSuper();
        // 所以用get获取数据,编译器表示老子怎么知道是你哪个老子,压根不晓得你的老子是啥,万一是禽兽呢,我只能object呀,你自己去判断
        Object object1 = aSuper.get(0);
        // 同样如果知道是哪个,强转
        Animal dd = (Animal) aSuper.get(0);
        // add方法,编译器表示,你的父类肯定是你儿子孙子的父类,多态支持我这么搞
        aSuper.add(new Person());
        aSuper.add(new Student());
    }
    /**
     * 接口:保存信息
     * 此处,必须传入person类或者父类集合
     *
     * @param list 下界通配符  ?表示Person的父类
     */
    public static void saveSuperList(List<? super Person> list) {
    }
    /**
     * 接口:保存信息
     * 此处,必须传入person类或者子类集合
     *
     * @param list ? extends Person 上界通配符
     */
    public static void saveExtendsList(List<? extends Person> list) {
    }
    public static List<? super Person> getSuper() {
        return new ArrayList<>();
    }
    public static List<? extends Person> getExtends() {
        return new ArrayList<>();
    }
    static class Animal {
    }
    static class Person extends Animal {
    }
    static class Student extends Person {
    }
}


相关文章
|
12月前
|
消息中间件 监控 Kubernetes
别再乱排查了!Kafka 消息积压、重复、丢失,根源基本都是 Rebalance!
大家好,我是小富~分享一次Kafka消息积压排查经历:消费者组因Rebalance导致消费能力骤降。本文详解Rebalance触发场景(消费者变更、分区扩容、订阅变化、超时等),剖析其引发的消息积压、重复消费、丢失等问题根源,并提供优化方案:调优超时参数、手动提交offset、启用粘性分配策略、保障消费幂等性。掌握这些,轻松应对Kafka常见故障!
2095 0
|
消息中间件 canal 存储
如何解决并发环境下双写不一致的问题?
在并发环境下,“双写不一致”指数据库与缓存因操作顺序或执行时机差异导致数据不匹配。解决核心是保证操作的原子性、顺序性或最终一致性。常见方案包括延迟双删、加锁机制、binlog同步、版本号机制和读写锁分离,分别适用于不同一致性要求和并发场景,需根据业务需求综合选择。
1047 0
|
机器学习/深度学习 人工智能 自然语言处理
指南:Grok中文版 _Grok 3 中文版本国内在线使用
Grok中文版都让用户能够不受限制地体验到最前沿的人工智能技术。通过这个平台,国内用户能够突破网络的束缚,尽情享受AI带来的便利与乐趣。
2979 4
|
测试技术 定位技术 API
万字长文:一文彻底搞懂Elasticsearch中Geo数据类型查询、聚合、排序
万字长文:一文彻底搞懂Elasticsearch中Geo数据类型查询、聚合、排序
96544 143
|
开发者 UED
鸿蒙next版开发:ArkTS组件通用属性(悬浮态效果)
在HarmonyOS 5.0中,ArkTS引入了悬浮态效果的控制属性,使开发者能为组件添加鼠标悬浮时的视觉反馈,增强用户体验。本文详解了hoverEffect属性及其常见效果(Auto、Scale、Highlight、None),并提供了示例代码,展示了如何为按钮设置悬浮效果。通过这些属性,开发者可以实现更生动和互动的界面。
1714 1
|
机器学习/深度学习 数据采集 语音技术
初学者指南:从零开始探索语音克隆与TTS技术
【10月更文挑战第20天】随着人工智能技术的飞速发展,语音克隆和文本转语音(Text-to-Speech, TTS)技术已经不再是科幻小说中的情节。如今,这些技术已经广泛应用于智能助手、语音导航、有声书等领域。作为一名对这些前沿技术感兴趣的初学者,本文将带你从零开始探索语音克隆与TTS技术,了解其基本原理,并动手实践制作自己的第一个语音克隆样本或TTS应用。
1803 0
|
网络协议 Java 应用服务中间件
tomcat 的并发能力分析
tomcat 的并发能力分析
1762 0
tomcat 的并发能力分析
|
机器学习/深度学习 人工智能 自然语言处理
【AI大模型】Transformers大模型库(十):repetition_penalty惩罚系数
【AI大模型】Transformers大模型库(十):repetition_penalty惩罚系数
1323 0
|
网络协议 安全 Linux
Scapy:Python发包收包利器
Scapy:Python发包收包利器
1139 0
|
JSON JavaScript 前端开发
CocosCreator3.8研究笔记(四)CocosCreator 脚本说明及使用(上)
CocosCreator3.8研究笔记(四)CocosCreator 脚本说明及使用(上)
1915 0
CocosCreator3.8研究笔记(四)CocosCreator 脚本说明及使用(上)

热门文章

最新文章