面试官:请问泛型擦除、泛型上界、泛型下界、PECS原则 是什么?

什么是泛型

泛型的本质是 类型参数化,解决类型爆炸的问题。

所谓泛型是指将类型参数化,以达到代码复用提高软件开发工作效率的一种数据类型。

然后我们要定义一个盘子 plate,注意这个盘子除了  装入食物food之外,还可以装其他的比如 小玩具。

为了装不同类型的食物,我们需要定义不同的盘子:

(1) 装水果的盘子  FruitPlate

(2) 装肉的盘子  MeatPlate

(3) 装苹果的盘子  ApplePlate

(4) 装香蕉的盘子  BananaPlate

.....

(N) 装云南苹果的盘子  YunnanFruitPlate

这就是盘子类型的  类型爆炸。

如何解决上面的类型爆炸问题呢? 这就要用到泛型。

那么盘子里的东西的类型,我们就用泛型

 

从这个例子看到:泛型是一种类型占位符,或称之为类型参数

如何使用呢?

public static void main(String[] args) {
    //创建一个装水果的盘子
    PlateDemo1<Fruit> plateDemo2 =new PlateDemo1<>(new Apple());

}

所谓泛型,就是 数据类型 指定为一个参数,在不创建新类的情况下,通过创建变量的时候去确定 数据的具体类型。

也就是说,在创建对象或者调用方法的时候才明确下具体的类型。

泛型定义:泛型类、泛型接口、泛型方法

泛型可以在类、接口、方法中使用,分别称为泛型类、泛型接口、泛型方法。

第一类:泛型类 定义格式:

修饰符 class 类名<类型> { }

 上面的例子就是 泛型类

class PlateDemo1<T> {

    //盘子里的东西
    private T someThing;
}

第二类:泛型方法

定义格式:

修饰符 <泛型类型> 返回值类型 方法名(类型 变量名) { }

 示例代码:

public <T> void demo(T t) { 
    ...
}

第三类:泛型接口

定义格式:

修饰符 interface 接口名<类型> { }

 示例代码:

public interface Generic<T> { 
    void demo(T t); 
}

 泛型接口的实现类

public class GenericImpl<T> implements Generic<T> { 
    public void demo(T t) { 
      ...
    } 
}

为什么不用Object做泛型化?

没有泛型的情况的下,好像Object也能实现简单的 泛化。

通过定义为类型Object的引用,来实现参数的“任意化”。

比如上面的例子的 泛型类

通过定义为类型Object的引用,来实现参数的“任意化”,结果如下

class PlateDemo1   {

    //盘子里的东西
    private Object  someThing;
}

Object实现参数的 “泛型化”、“任意化”带来的缺点是:要做显式的强制类型转换。

参数类型强制转换有一个大大降低代码复用性和扩展性的坏处:

  • 首先,要求开发者对实际参数类型可预知。

  • 其次,不利于未来的 扩展。

泛型的两大好处

1、避免了强制类型转换,提高代码的复用性和扩展性

泛型中,所有的类型转换都是自动和隐式的,不需要强制类型转换,可以提高代码的重用率,再加上明确的类型信息,代码的可读性也会更好。

2、把运行时期的问题提前到了编译期,编译时的类型检查,使程序更加健壮

使用普通的Object泛化,对于强制类型转换错误的情况,编译期不会提示错误,在运行的时候才出现异常,这是一个安全隐患。

泛型的好处是在编译期检查类型安全,并能捕捉类型不匹配的错误,避免运行时抛出类型转化异常ClassCastException,将运行时错误提前到编译时错误,消除安全隐患。

正是由于以上两点原因,泛型得到了广泛的应用。

比如Java中,所有的标准集合接口都是泛型化的:Collection<V>List<V>Set<V>Map<K,V>

泛型的上界/ 上界通配符(Upper Bounds Wildcards)

现在我定义一个“水果盘子”,用来装苹果, 逻辑上水果盘子当然可以装苹果。

那么,一个“装苹果的盘子”,能转换成一个“装水果的盘子”吗?

看下面的例子

那么,一个“装苹果的盘子”,能转换成一个“装水果的盘子”吗? 答案是不行的。

编译器 的逻辑是这样的:

  • 苹果  is-a  水果

  • 装苹果的盘子 not  is-a   装水果的盘子

也就是说:就算 苹果  is-a 水果,但容器之间是没有继承关系的。

怎么办?这里用到了  泛型上界。   泛型上界是这么定义的:

<?extends 基类B> 

<?extends 基类B>  表示泛型实参类型的上界是“基类B”,

换句话说,泛型实参的类型,可能是“基类B” 或者是“基类B”的子类;

修改之后的例子如下,使用 泛型上界通配符(Upper Bounds Wildcards)后,编译器就不报错误了:

使用(Upper Bounds Wildcards)通配符作为泛型实参,所定义 PlateDemo1<?extends Fruit> 引用,可以 覆盖下图中方框内部的所有子类的  泛型对象。

<?extends T>  表示类型的上界,参数化类型可能是T 或者是 T的子类;

PlateDemo1<?extends Fruit> 引用,可以 覆盖下图中方框内部的所有子类的  泛型对象,编译器都不报错,下面的代码如下:

为啥<?extends Fruit> 叫做 上界,而不叫下届? 原因是:这个通配符,定义了实参的类型上限 为 Fruit,具体如下图:

上界通配符(Upper Bounds Wildcards)的问题 

上界通配符(Upper Bounds Wildcards)的作用,实现了 子类泛型对象 到 父类Java泛型对象之间的引用转换。

但是,这样的引用转换也有一定的副作用。

具体如下:

通过例子可以看到:

(1)往基类盘子,set( ) 任何对象,都 失效了

(2)从基类盘子,get ( )  对象的引用,返回 类型是上界对象,  这个还是 可以的

简单来说:上界<? extends T>不能往里存,只能往外取

所以,上界通配符(Upper Bounds Wildcards)什么时候用,什么时候不用呢:

(1)当从集合中获取元素进行操作的时候用,可以用当前元素的类型接收,也可以用当前元素的父类型接收。

(2)往集合中添加元素时,不能用上界通配符(Upper Bounds Wildcards)。

泛型的下界/ 下界通配符(Lower Bounds Wildcards)

往集合中添加元素时,不能用上界通配符(Upper Bounds Wildcards)。

怎么办呢?

Java也提供了一种通配符,叫做 泛型的下界/ 下界通配符(Lower Bounds Wildcards)。

泛型上界是这么定义的:

<?super 子类C> 

 <?super 子类C>  表示泛型实参类型的下界是“子类C”,

 <? super T>  表示 T是类型下边界,参数化类型是此T类型的超类型,直至object;

下界/ 下界通配符(Lower Bounds Wildcards)的问题 

下界/ 下界通配符(Lower Bounds Wildcards) 作用,实现了 复类泛型对象 到 子类Java泛型对象之间的引用转换。

但是,这样的引用转换也有一定的副作用。

具体如下:

通过例子可以看到:

(1)往基类盘子,set( ) 任何子类对象,都是OK的

(2)从基类盘子,get ( )  对象的引用是编译错误的,除非是Object类型

简单来说:下界<? super T>可以往里存,但不能向外取,要取只能取Object对象

所以,下界/ 下界通配符(Lower Bounds Wildcards)什么时候用,什么时候不用呢:

(1)当往集合中添加元素时候用,既可以添加T类型对象,又可以添加T的子类型对象

(2)当从集合get ( )  对象的引用时,不能用上界通配符(Upper Bounds Wildcards)。除非get 的是Object类型

PECS原则

PECS原则的全称是Producer Extends Consumer Super,很多小伙伴从没听说过,面试的时候,只要面试官一问,大部分都是一脸懵逼。

什么是PECS(Producer Extends Consumer Super)原则?PECS原则全称"Producer Extends, Consumer Super",即上界生产,下界消费。

  • Producer Extends 上界生产,就是 生产者使用  “? extends T”通配符。

  • Consumer Super 下界消费,就是消费者使用  “? super T”通配符

最终PECS  (Producer Extends Consumer Super ) 原则

  • 频繁往外读取内容的,适合用上界Extends。

  • 经常往里插入的,适合用下界Super。

1. Producer Extends 上界生产

Producer Extends 上界生产,就是 生产者使用  “? extends T”通配符。

以“? extends T”声明的集合,不能往此集合中添加元素,所以它也只能作为生产者

所以,使用 “? extends T”  上界,能轻松地成为 producer 生产者,完成

  • 读取元素

  • 迭代元素

这就是 Producer Extends 上界生产,就是 生产者使用  “? extends T”通配符。

2. Consumer Super 下界消费

Consumer Super 下界消费,就是消费者使用  “? super T”通配符

在通配符的表达式中,只有“? super T”能添加元素,所以它能作为消费者(消费其他通配符集合)。

当然,针对采用“? super T”通配符的集合,对其遍历时需要多一次转型。

总之 PECS就是:

1、频繁往外读取内容的,适合用上界Extends。

2、经常往里插入的,适合用下界Super

明白了泛型、泛型的上界,泛型的下届之后,带大家来回答这个面试的核心问题:什么是泛型的擦除。

泛型的类型擦除

前面讲到,泛型的本质是 类型参数化,解决类型爆炸的问题。比如:如果我们的代码中存在很多的 食物类型, 继承关系如下

没有泛型,为了实现去装不同类型的食物,我们需要定义不同的盘子: 

(1) 装水果的盘子  FruitPlate

(2) 装肉的盘子  MeatPlate

(3) 装苹果的盘子  ApplePlate

(4) 装香蕉的盘子  BananaPlate

.....

(N) 装云南苹果的盘子  YunnanFruitPlate

如何解决上面的类型爆炸问题呢? 这就要用到泛型。

而使用泛型,我们定义一个就可以了:

class PlateDemo1<T> {

    //盘子里的东西
    private T someThing;

    public PlateDemo1(T t) {
        someThing = t;
    }
    ....
}

这样,就避免 了 盘子类型的  类型爆炸。尤其在Java中的集合类,如果不用泛型,不知道要定义多少的具体集合类。

那么 Java中的泛型,有一个 类型擦除 的特点:

  • java的泛型,只在编译期有效。

  • 编译之后的字节码,已经抹除了泛型信息。

所谓的类型擦除(type erasure),指的是泛型只在编译时起作用,在进入JVM之前,泛型会被擦除掉,根据泛型定义的形式而被替换为相应的类型。这也说明了Java的泛型其实是伪泛型。

类型擦除简单来说,泛型类型在逻辑上可以看成是多个不同的类型,实际上都是相同类型。

比如:

Food food = new Fruit(); // 没问题
ArrayList<Food> list= new ArrayList<Fruit>(); // 报错

或者说下面的ArrayList ,在逻辑上看,可以看成是多个不同的类型,实际上都是相同类型

看下面的例子

类型参数在运行中并不存在,这意味着:

  • 运行期间,泛型不会添加任何的类型信息;

  • 不能依靠泛型参数,进行类型转换。

Java泛型的实现是靠类型擦除技术实现的,类型擦除是在编译期完成的,泛型擦除怎么做呢?

  • 在编译期,编译器会将泛型的类型参数都擦除成它指定的原始限定类型

  • 如果没有指定的原始限定类型则擦除为Object类型,之后在获取的时候再强制类型转换为对应的类型,

  • 因此生成的Java字节码中是不包含泛型中的类型信息的,即运行期间并没有泛型的任何信息。

无界泛型擦除

当泛型类型被声明为一个具体的泛型标识,或一个无界通配符时,泛型类型将会被替代为Object

这也比较容易理解,如  List<?>,PlateDemo1<?>,   当获取元素的时,因为不能够确定具体的类型,所以只能使用Object来接收,

在擦除的时候也是一样的道理,无法确定具体类型,所以擦除泛型时会将其替换为Object类型,如:

上界擦除 

当泛型类型被声明为一个上界通配符时,泛型类型将会被替代为相应上界的类型。

主要,这里的上界,指的是用于类型定义场景里边的上界:

而不是变量定义场景里边用到到泛型上界,如下:

 List<? extends Fruit> producer =...;

 用泛型上界定义class的时候,指的是用于类型定义,泛型类型将会被替代为相应上界的类型。

下界擦除 

下界通配符的擦除,同无界通配符

下届只能定义引用的时候用,在定义类型的时候用不了,所以下界擦除只能替换为Object

下界擦除只能替换为Object

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/347360.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Kubernetes】深入了解Kubernetes(K8s):现代容器编排的引领者

欢迎来到英杰社区&#xff1a; https://bbs.csdn.net/topics/617804998 欢迎来到阿Q社区&#xff1a; https://bbs.csdn.net/topics/617897397 作者简介&#xff1a; 辭七七&#xff0c;目前大二&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xf…

leetcode — 贪心算法— 买卖股票的最佳时机

1 题目描述 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易中获取…

无限学模式-“科研创新的加速器:全面掌握ChatGPT,推动研究方法和工作模式现代化!“

2023年随着OpenAI开发者大会的召开&#xff0c;最重磅更新当属GPTs&#xff0c;多模态API&#xff0c;未来自定义专属的GPT。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义&#xff0c;不亚于互联网和个人电脑的问世。360创始人周鸿祎认为未来各行各业如果不能搭上这班车…

好书推荐丨豆瓣评出9.2高分!Python编程入门就看蟒蛇书

目录 写在前面 内容简介 业内专家推荐 编辑推荐 资源丰富 作者介绍 Q&A 粉丝福利 写在后面 写在前面 在这日新月异的科技新时代&#xff0c;编程如同一把万能钥匙&#xff0c;为无数人打开了通向无限可能的大门。而在众多编程语言中&#xff0c;Python无疑是最耀…

【CANoe使用大全】——DBC数据库制作

文章目录 1.DBC数据库选择1.1.DBC模板选择1.3. 新建报文1.4. 新建信号1.5.数值表建立 2. DBC导入 1.DBC数据库选择 首先找到DBC编辑器入口 1.1.DBC模板选择 举例说明&#xff1a; 新建选择CANFD的模板 1.3. 新建报文 注意上图中报文周期“Cycle Time”处于不可编辑状态…

【C++】介绍STL中list容器的常用接口

目录 一、STL中的list简介 二、构造函数 2.1 默认构造函数 2.2 填充构造&#xff08;用n个相同的值构造&#xff09; 2.3 迭代器构造 2.4 拷贝构造和赋值运算符重载 三、迭代器 3.1 正向迭代器 3.2 反向迭代器 四、容量相关 4.1 获取list中有效数据的个数 4.2 判…

【Web前端开发基础】CSS3之空间转换和动画

CSS3之空间转换和动画 目录 CSS3之空间转换和动画一、空间转换1.1 概述1.2 3D转换常用的属性1.3 3D转换&#xff1a;translate3d&#xff08;位移&#xff09;1.4 3D转换&#xff1a;perspective&#xff08;视角&#xff09;1.5 3D转换&#xff1a;rotate3d&#xff08;旋转&a…

使用StrictMode优化Android应用程序的ANR率

使用StrictMode优化Android应用程序的ANR率 本文将解释StrictMode是什么以及如何在Android应用程序中使用它作为ANR观察器。 什么是StrictMode以及为什么使用它&#xff1f; StrictMode是帮助开发人员防止ANR并减少在Android系统中产生ANR的机会的工具之一。 从developer.a…

抖音跳转微信公众号是怎么实现的丨数灵通

抖音是一款非常流行的社交媒体应用程序&#xff0c;用户可以在其中分享短视频和互动内容。许多用户希望通过抖音来引流到他们的微信公众号&#xff0c;以扩大影响力并吸引更多粉丝。以下是一些关于如何在抖音上跳转到微信公众号的科普信息&#xff1a; 1.信息流广告&#xff1a…

elementplus Dialog 对话框设置距离页面顶部的距离

默认为 15vh&#xff0c;当弹窗过于高的时候&#xff0c;这个距离其实是不合适的 <el-dialogv-model"dialogVisible"title"Tips"width"30%":before-close"handleClose"top"6vh"><span>This is a message</s…

leetcode:2859. 计算 K 置位下标对应元素的和(python3解法)

难度&#xff1a;简单 给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。 请你用整数形式返回 nums 中的特定元素之 和 &#xff0c;这些特定元素满足&#xff1a;其对应下标的二进制表示中恰存在 k 个置位。 整数的二进制表示中的 1 就是这个整数的 置位 。 例如&#xf…

如何实现激光雷达运动补偿,这篇就够了

目录 激光雷达为什么会存在运动畸变 激光雷达如何运动去畸变 C++实践激光雷达运动补偿(辅助传感器) 实践激光雷达ICP运动补偿 参考文献 激光雷达为什么会存在运动畸变 首先要理解为什么会产生运动畸变。激光雷达扫描物体形成点云的过程自身伴随着旋转运动,每次激…

嵌入式-stm32-江科大-EXTI外部中断

一&#xff1a;EXTI外部中断&#xff08;external interrupt&#xff09; 1.1 STM32 中断系统 中断是指在主程序运行过程中&#xff0c;出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;使得CPU暂停当前的程序&#xff0c;转而去处理中断程序&#xff0c;…

Termux结合内网穿透实现无公网ip远程SFTP传输文件

目录 前言 1. 安装openSSH 2. 安装cpolar 3. 远程SFTP连接配置 4. 远程SFTP访问 4. 配置固定远程连接地址 结语 作者简介&#xff1a; 懒大王敲代码&#xff0c;计算机专业应届生 今天给大家聊聊Termux结合内网穿透实现无公网ip远程SFTP传输文件&#xff0c;希望大家能…

【基础算法练习】二分模板

文章目录 二分模板题二分的思想C 版本的二分整数二分模板 Golang 版本的二分整数二分模板 例题&#xff1a;在排序数组中查找元素的第一个和最后一个位置题目描述C 版本代码Golang 版本代码 二分模板题 704. 二分查找&#xff0c;这道题目是最经典的二分查找&#xff0c;使用于…

华为产业链之车载激光雷达

一、智能汽车 NOA 加快普及&#xff0c;L3 上路利好智能感知硬件 1、感知层是 ADAS 最重要的一环 先进驾驶辅助系统 &#xff08;ADAS&#xff0c; Advanced driver-assistance system&#xff09;分“感知层、决策层、执行层”三个层级&#xff0c;其中感知层是最重要的一环…

不同页面加载对爬虫的影响

目录 前言 1. 不同页面加载方式对爬虫的影响 1.1 静态页面加载 1.2 动态页面加载 2. 使用代理IP进行访问 总结 前言 在进行网络爬虫的过程中&#xff0c;不同的网页加载方式可以对爬虫的效率和稳定性产生重要影响。有些网站可能会限制对其服务器的访问频率&#xff0c;如果…

怎么把ico图片转png?图片格式转换的快捷方法

ICO是一种常用的图标文件格式&#xff0c;广泛用于软件应用的图标设计&#xff0c;然而&#xff0c;ICO格式的图片分辨率通常较低&#xff0c;因此在某些平台上无法满足上传要求&#xff0c;为了解决这个问题&#xff0c;我们通常需要将ICO格式转换为常见的PNG格式或其他常用格…

GuitarPro和Earmaster那个适合新手

许久没发文了&#xff0c;最近在网上刷到了一位音乐UP主从容Free&#xff0c;他把自己对GuitarPro和Earmaster这2款软件的使用感受进行了详细分享&#xff0c;还没看过的朋友可以戳下面的链接跳转到小破站看完整的&#xff1a; 我不允许还有人不知道这个学吉他的神器&#xff…

YOLO 自己训练一个模型

一、准备数据集 我的版本是yolov8 8.11 这个目录结构很重要 ultralytics-main | datasets|coco|train|val 二、训练 编写yaml 文件 # Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..] path…