02-JDK新特性-泛型

泛型

什么是泛型

泛型是JDK5中引入的特性,它提供了编译时类型安全检测机制,该机制允许在编译是检测到非法的类型。

它的本质是参数化类型,也就是说操作的数据类型被指定为一个参数。

也就是将类型有原来的具体类型参数化,然后在使用/调用时传入具体的类型。

这种参数类型可以用在类、方法和接口中,分别被称为泛型类、泛型方法、泛型接口。

泛型定义格式

<类型>:指定一种类型的格式,这里的类型可以看成是形参。

<类型1,类型2…>:指定多种类型的格式,多种类型之间用逗号隔开,这里的类型可以看成是形参。

将来具体调用时给定类型可以看成实参,并且实参的类型只能是引用数据类型。

泛型的好处

  1. 把运行时期的问题提前到编译期间
  2. 避免了强制类型转换

案例

ArrayList使用泛型与不使用泛型比较

package main.java.demo1;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;/*** ArrayList 使用泛型与不使用泛型比较** @author Anna.* @date 2024/4/1 21:34*/
public class GenericDemo1 {public static void main(String[] args) {List<String> listStr = new ArrayList<String>();listStr.add("123");
//        listStr.add(123);  // 编译时会报错提示不允许设置数据类型与设置泛型类型不一致Iterator<String> stringIterator = listStr.iterator();while (stringIterator.hasNext()){String str = stringIterator.next(); // 避免强制类型转换问题 next()拿到的数据就是设置的泛型数据System.out.println(str);}List list = new ArrayList();list.add("123");list.add(123);Iterator iterator = list.iterator();while (iterator.hasNext()){Integer b = (Integer) iterator.next();  // 这里会报数据转换异常System.out.println(b);}}
}

执行结果

在这里插入图片描述

泛型的使用

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

泛型类

格式: 修饰符 class 类名<类型>{}

范例:
public class Generic<T>{}

注意:

此处T可以随便写为任意表示,常见的如T、E、K、V等形式的参数常用于表示泛型。

Java 常见的泛型标识以及其代表含义如下:

T :代表一般的任何类。
E :代表 Element 元素的意思,或者 Exception 异常的意思。
K :代表 Key 的意思。
V :代表 Value 的意思,通常与 K 一起配合使用。
N :代表 Number(数值类型)
R :代表 return(返回值)

泛型方法

格式: 修饰符 <类型> 返回值类型 方法名(类型 变量名称){}

范例:
public <T> void show(T t){}

泛型接口

格式: 修饰符 interface 接口名称<类型>{}

范例:
public interface Generic<T>{}

示例

定义泛型接口Show.java

package main.java.demo2;/*** 定义泛型接口** @author Anna.* @date 2024/4/1 22:03*/
public interface Show<T> {void show(T t);
}

定义泛型接口实现类ShowImpl.java

package main.java.demo2;/*** 定义泛型接口类型实现** @author Anna.* @date 2024/4/1 22:03*/
public class ShowImpl<T> implements Show<T> {@Overridepublic void show(T t) {System.out.println("泛型接口t = " + t);}
}

定义泛型类GenericDo.java

package main.java.demo2;/*** 定义泛型类** @author Anna.* @date 2024/4/1 22:05*/
public class GenericDo<T> {public void show(T t){System.out.println("泛型类t = " + t);}
}

定义泛型方法GenericDo1.java

package main.java.demo2;public class GenericDo1 {/*** 定义泛型方法** @param t* @return void* @author Anna.* @date 2024/4/1 22:11*/public <T> void show(T t){System.out.println("泛型方法t = " + t);}
}

测试GenericDemo2.java

package main.java.demo2;public class GenericDemo2 {public static void main(String[] args) {// 调用泛型类GenericDo<String> genericDo = new GenericDo<String>();genericDo.show("123");
//        genericDo.show(false); // 编译会报错GenericDo<Boolean> genericDo1 = new GenericDo<Boolean>();genericDo1.show(false);// 调用泛型接口Show<String> show1 = new ShowImpl<String>();show1.show("123");
//        show1.show(false); // 编译会报错Show<Boolean> show2 = new ShowImpl<Boolean>();show2.show(false);// 调用泛型方法GenericDo1 genericDo3 = new GenericDo1();genericDo3.show(false);genericDo3.show("123");}
}

执行结果

在这里插入图片描述

类型通配符

类型通配符

为了表示各种泛型List的父类,可以使用类型通配符

格式: <?>

List<?>:表示元素类型未知的List,它的元素可以匹配<font color="red"><b>任何的类型</b></font>。<br/>
这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中。
类型通配符上限

如果说我们不希望List<?> 是任何泛型List的父类,只希望它代表一类泛型List的父亲,可以使用类型通配符的上限

格式: <? extends 类型>

List<? extends Number>:它表示的类型是Number或者其子类型。
类型通配符下限

除了可以指定类型通配符的上限,我们也可以指定类型通配符的下限

格式: <? supper 类型>

List<? supper Number>:它表示的类型是Number或者其父类型。
示例

首先我们看一下Number的继承关系,如下图:

从图中我们可以看出,Number的父类是Object,子类包含 Byte, Integer, Long等

在这里插入图片描述

测试代码

package main.java.demo3;import java.util.ArrayList;
import java.util.List;public class GenericDemo03 {public static void main(String[] args) {// 测试通配符上限List<? extends Number> list1 = new ArrayList<Object>(); // 编译报错List<? extends Number> list2 = new ArrayList<Number>();List<? extends Number> list3 = new ArrayList<Integer>();// 测试通配符下限List<? super Number> list4 = new ArrayList<Object>();List<? super Number> list5 = new ArrayList<Number>();List<? super Number> list6 = new ArrayList<Integer>();  // 编译报错}
}

测试结果

在这里插入图片描述

<? extends T>与<? super T> 对比

结论:

(1)对于<? extends 类型>,编译器将只允许读操作,不允许写操作。即只可以取值,不可以设值。

(2)对于<? super 类型>,编译器将只允许写操作,不允许读操作。即只可以设值(比如 set 操作),不可以取值(比如 get 操作)。

已 Java 标准库的 Collections 类定义的 copy() 方法为例子

import java.util.List;public class Collections {// 把 src 的每个元素复制到 dest 中:public static <T> void copy(List<? super T> dest, List<? extends T> src) {for (int i = 0; i < src.size(); i++) {// 获取 src 集合中的元素,并赋值给变量 t,其数据类型为 TT t = src.get(i);// 将变量 t 添加进 dest 集合中 dest.add(t);// 添加元素进入 dest 集合中}}
}

如果反过来,我们可以看到编译器编译失败

import java.util.List;public class Collections {// 把 dest 的每个元素复制到 src 中:public static <T> void copy(List<? super T> dest, List<? extends T> src) {for (int i = 0; i < src.size(); i++) {// 获取 dest 集合中的元素,并赋值给变量 t,其数据类型为 TT t = dest.get(i);// 将变量 t 添加进 src 集合中src.add(t);// 添加元素进入 src 集合中}}
}

编译结果

在这里插入图片描述

copy() 方法的另一个好处是可以安全地把一个 List< Integer >添加到 List< Number >,但是无法反过来添加。

这个很好理解,List< Number > 集合中可能有 Integer、Float 等对象,所以肯定不能复制到List< Integer > 集合中;而 List< Integer > 集合中只有 Integer 对象,因此肯定可以复制到 List< Number > 集合中。

PECS 原则

我们何时使用 extends,何时使用 super 通配符呢?为了便于记忆,我们可以用 PECS 原则:Producer Extends Consumer Super。

即:如果需要返回 T,则它是生产者(Producer),要使用 extends 通配符;如果需要写入 T,则它是消费者(Consumer),要使用 super 通配符。

还是以 Collections 的 copy() 方法为例:

public class Collections {public static <T> void copy(List<? super T> dest, List<? extends T> src) {for (int i = 0; i < src.size(); i++) {T t = src.get(i); // src 是 producerdest.add(t); // dest 是 consumer}}
}

需要返回 T 的 src 是生产者,因此声明为List<? extends T>,需要写入 T 的 dest 是消费者,因此声明为List<? super T>。

类型擦除

泛型的本质是将数据类型参数化,它通过擦除的方式来实现,即编译器会在编译期间擦除代码中的所有泛型语法并相应的做出一些类型转换动作。

换而言之,泛型信息只存在于代码编译阶段,在代码编译结束后,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。

也就是说,成功编译过后的 class 文件中不包含任何泛型信息,泛型信息不会进入到运行时阶段。

假如我们给 ArrayList 集合传入两种不同的数据类型,并比较它们的类信息。代码如下:

public class GenericType {public static void main(String[] args) {ArrayList<String> arrayString = new ArrayList<String>();ArrayList<Integer> arrayInteger = new ArrayList<Integer>();System.out.println(arrayString.getClass() == arrayInteger.getClass());// true}
}

在这个例子中,我们定义了两个 ArrayList 集合,不过一个是 ArrayList< String>,只能存储字符串。一个是 ArrayList< Integer>,只能存储整型对象。

我们通过 arrayString 对象和 arrayInteger 对象的 getClass() 方法获取它们的类信息并比较,发现结果为true。

明明我们在 <> 中传入了两种不同的数据类型,按照上文所说的,它们的类型参数 T 不是应该被替换成我们传入的数据类型了吗,那为什么它们的类信息还是相同呢?

这是因为,在编译期间,所有的泛型信息都会被擦除, ArrayList< Integer > 和 ArrayList< String >类型,在编译后都会变成ArrayList< Object >类型。

再看一个例子,假设定义一个泛型类如下:

public class Caculate<T> {private T num;
}

在该泛型类中定义了一个属性 num,该属性的数据类型是泛型类声明的类型参数 T ,这个 T 具体是什么类型,我们也不知道,它只与外部传入的数据类型有关。将这个泛型类反编译。

代码如下:

public class Caculate {public Caculate() {}// 默认构造器,不用管private Object num;// T 被替换为 Object 类型
}

可以发现编译器擦除了 Caculate 类后面的泛型标识 < T >,并且将 num 的数据类型替换为 Object 类型,而替换了 T 的数据类型我们称之为原始数据类型。

那么是不是所有的类型参数被擦除后都以 Object 类进行替换呢?

答案是否定的,大部分情况下,类型参数 T 被擦除后都会以 Object 类进行替换;而有一种情况则不是,那就是使用到了 extends 和 super 语法的有界类型参数。

再看一个例子,假设定义一个泛型类如下:

public class Caculate<T extends Number> {private T num;
}

将其反编译:

public class Caculate {public Caculate() {}// 默认构造器,不用管private Number num;
}

可以发现,使用到了 extends 语法的类型参数 T 被擦除后会替换为 Number 而不再是 Object。
extends 和 super 是一个限定类型参数边界的语法,extends 限定 T 只能是 Number 或者是 Number 的子类。
也就是说,在创建 Caculate 类对象的时候,尖括号 <> 中只能传入 Number 类或者 Number 的子类的数据类型,所以在创建 Caculate 类对象时无论传入什么数据类型,Number 都是其父类,于是可以使用 Number 类作为 T 的原始数据类型,进行类型擦除并替换。

gitee源码

git clone https://gitee.com/dchh/JavaStudyWorkSpaces.git

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【CVE复现计划】CVE-2023-27179

CVE-2023-27179 简介&#xff1a; GDidees CMS v3.9.1及更低版本被发现存在本地文件泄露漏洞&#xff0c;漏洞通过位于 /_admin/imgdownload.php 的 filename 参数进行利用。 影响版本&#xff1a; GDidees CMS v3.9.1及更低版本 POC: /_admin/imgdownload.php?filename/fla…

MATLAB 自定义中值滤波(54)

MATLAB 自定义中值滤波(54) 一、算法介绍二、算法实现1.原理2.代码一、算法介绍 中值滤波,是一种常见的点云平滑算法,改善原始点云的数据质量问题,MATLAB自带的工具似乎不太友好,这里提供自定义实现的点云中值滤波算法,具体效果如下所示: 中值滤波前: 中值滤波后:…

前端性能优化-Table渲染速度优化

教务系统-排课页面性能优化总结 一、前言 在公司教务系统中,排课页面慢的令人发指,在某些情况由于数据量大导致页面主进程卡死,遂组织进行一次排查优化,现记录一下 二、效果对比 以下数据均为UAT环境 Performence对比 更改前: 主进程渲染时间为 8s 教务系统-排课页面性…

MHA的实验部署

一、前期准备 准备四台虚拟机&#xff0c;一台主服务器&#xff0c;一台管理服务器&#xff0c;两台从服务器 在开始之前先要关闭所有服务器的防火墙&#xff0c;以免有一些麻烦 二、实际操作 2.1 配置主服务器 2.2 配置从服务器1和2 2.3 给主从服务器实现软链接 2.4 配置mysql…

TypseScript再学习之类型别名和接口(10)

先看类型别名&#xff1a;使用关键字 type 声明,注意有等于号额 // 类型别名 使用关键字 type 声明,注意有等于号额 type Cat {name: string; }; let huahua: Cat {name: "花花", };type和interface不同之处在于&#xff1a;interface 是可以自动合并类型的&#…

【单片机 5.3开关检测】

文章目录 前言一、5.3开关检测1.1没按键按下的1.2有按键按下的 二、改进1.改进 三、独立键盘3.1为什么要取反3.2 实用的按键 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 课程需要&#xff1a; 提示&#xff1a;以下是本篇文章正文内容&#xf…

春暖助学 梦想启航

&#xff08;通讯员&#xff1a;赵灿飞 图&#xff1a;杨美、孙红浪&#xff09; 春风拂面暖阳斜&#xff0c;爱心助学谱华章。为弘扬中华民族传统美德&#xff0c;动员社会力量&#xff0c;传播社会爱心&#xff0c;缓解宁乡西部特殊家庭学子学业面临的实际困难&#xff…

CorePoolExecutor夺命连环问?看你可以接受几招?

一、前言 今天我在看why技术的时候&#xff0c;看到了这个。发现这个没有全部的八股回答&#xff1f;于是我就结合自己的经验&#xff0c;分享下八股 二、八股问答 2.1了解JDK Executors线程池吗? Executor就是一个线程池框架&#xff0c;在开发中如果需要创建线程可优先考…

JMeter自定义日志与日志分析

1 JMeter日志概览 JMeter与Java程序一样&#xff0c;会记录事件日志&#xff0c;日志文件保存在bin目录中&#xff0c;名称为jmeter.log。当然&#xff0c;我们也可以在面板中直接察看日志&#xff0c;点击右上角黄色标志物可以打开日志面板&#xff0c;再次点击收起。 可见&…

从入门到实战:vue3路由知识点

本人在B站上关于vue3的尚硅谷的课程&#xff0c;以下是整理一些笔记。 1.两个知识点 1.路由组件通常存放在pages 或 views文件夹&#xff0c;一般组件通常存放在components文件夹。 组件可以分为&#xff1a; 1. 一般组件&#xff1a;亲手写标签出来的 2. 路由组件&#…

非关系型数据库——Redis配置与优化

目录 一、关系型数据库和非关系型数据库 1.定义 1.1关系型数据库 1.2非关系型数据库 2.非关系型数据库产生的背景 3.关系型数据库和非关系型数据库区别 3.1适用性不同 3.2数据一致性要求不同 3.3数据模型不同 3.4数据查询语言不同 3.5数据存储方式不同 3.6扩展方式…

用Servlet实现一个简单的表白墙

1. 准备工作 创建项目,引入依赖...... 将静态页面放到项目中(放在webapp目录下): 当前,这个表白墙页面,已经可以输入内容,点击提交之后也能显示内容,后续后端要做的工作即: ①存档 用户点提交的时候,把刚才输入的内容通过网络传输给服务器,由服务器保存这个数据. ②读档 …

[中级]软考_软件设计_计算机组成与体系结构_04_寻址地址

寻址地址 概念指令的概念 寻址方式立即寻址方式直接寻址方式间接寻址方式寄存器寻址方式寄存器间接寻址方式往年真题 概念 指令的概念 一条指令就是机器语言的一个语句&#xff0c;它是一组有意义的二进制代码&#xff0c;指令的基本格式如下&#xff1a; 操作码字段地址码字…

基于springboot实现企业客户管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现企业客户管理系统演示 摘要 本论文主要论述了如何使用JAVA语言开发一个企业客户管理系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述企…

鸿蒙HarmonyOS应用开发之Node-API支持的数据类型和接口

Node-API的数据类型 napi_status 是一个枚举数据类型&#xff0c;表示Node-API接口返回的状态信息。 每当调用一个Node-API函数&#xff0c;都会返回该值&#xff0c;表示操作成功与否的相关信息。 typedef enum {napi_ok,napi_invalid_arg,napi_object_expected,napi_stri…

计算机网络面试问题(一)

1.在浏览器中输⼊URL并按下回⻋之后会发⽣什么 2.TCP三次握⼿的过程,为什么三次握手 TCP&#xff08;传输控制协议&#xff09;的三次握⼿是建⽴⽹络连接的过程&#xff0c;确保通信双⽅能够正确地进⾏数据传输。 第⼀次握⼿&#xff08;SYN&#xff09;&#xff1a; 客户端&am…

Php_Code_challenge13

题目&#xff1a; 答案&#xff1a; 解析&#xff1a; 开启一个会话&#xff0c;在SESSION变量"nums"为空时则对"nums","time","whoami"进行赋值&#xff0c;并在120秒后关闭会话&#xff0c;创建一个变量"$value"…

第6章 数据存储操作

思维导图 6.1 引言 数据存储与操作包括对存储数据的设计、实施和支持&#xff0c;最大化实现数据资源的价值&#xff0c;贯穿于数据创建/获取到处置的整个生命周期。 6.1.1 业务驱动因素 数据存储与操作活动对于依赖数据的企业来说非常关键&#xff0c;这些活动的主要驱动因素是…

Meta Pixel:助你实现高效地Facebook广告追踪

Meta Pixel 像素代码是用來衡量Facebook广告效果的一个官方数据工具&#xff0c;只要商家有在Facebook上投放广告就需要串联Meta Pixel 像素代码来查看相关数据。 它本质上是一段 JavaScript 代码&#xff0c;安装后可以让用户在自己网站上查看到访客活动。它的工作原理是加载…

记windows配置maven环境变量

配置环境变量 idea中配置maven