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> {
    @Override
    public 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,其数据类型为 T
            T 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,其数据类型为 T
            T 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 是 producer
            dest.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

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

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

相关文章

MySQL 优化及故障排查

目录 一、mysql 前置知识点 二、MySQL 单实例常见故障 故障一 故障二 故障三 故障四 故障五 故障六 故障七 故障八 三、MySQL 主从故障排查 故障一 故障二 故障三 四、MySQL 优化 1.硬件方面 &#xff08;1&#xff09;关于 CPU &#xff08;2&#xff09;关…

使用虚拟引擎为AR体验提供动力

Powering AR Experiences with Unreal Engine ​​​​​​​ 目录 1. 虚拟引擎概述 2. 虚拟引擎如何为AR体验提供动力 3. 虚拟引擎中AR体验的组成部分是什么&#xff1f; 4. 使用虚拟引擎创建AR体验 5. 虚拟引擎中AR的优化提示 6. 将互动性融入AR与虚拟引擎 7. 在AR中…

Java项目:83 springboot知识管理系统

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 本知识管理系统有管理员和用户两个角色。 管理员功能有个人中心&#xff0c;用户管理&#xff0c;文章分类管理&#xff0c;文章信息管理&…

Excel 数据-分列的三个经常用法

Case 1 &#xff1a;有时候数据导出时如果没有电子表格的话&#xff0c;只能导出本地文件&#xff0c;如下图情况&#xff1a; 可以使用数据-分列处理数据&#xff1a; 原来是因为SAP导出数据没有完成的原因&#xff0c;或者关闭Excel重新打开试一下。 重新打开后可以输入了 C…

NoSQL之 Redis配置

目录 关系数据库与非关系型数据库 关系型数据库&#xff1a; ●非关系型数据库 关系型数据库和非关系型数据库区别&#xff1a; &#xff08;1&#xff09;数据存储方式不同 &#xff08;2&#xff09;扩展方式不同 对事务性的支持不同 非关系型数据库产生背景 Redis简介…

C++初阶:5.STL简介(了解)

STL简介&#xff08;了解&#xff09; 一.什么是STL STL(standard template libaray-标准模板库)&#xff1a;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的组件库&#xff0c;而且是一个包罗数据结构与算法的软件框架。 二. STL的版本 原始版本 Alexander Stepan…

绝地求生:Anti-ESP推进对于PUBG哪些影响?

大家好&#xff0c;我是闲游盒。近期&#xff0c;PUBG开发团队开发信介绍了反作弊团队讨论的新话题 - ESP&#xff08;Extra Sensory Perception&#xff09;。在详细看完开发信以后&#xff0c;今天跟大家简单聊聊关于Anti-ESP。 ESP是一种非法软件&#xff0c;允许玩家在游戏…

MacBook 访达使用技巧【mac 入门】

快捷键 打开访达搜索窗口默认快捷键【⌥ ⌘ 空格键】可以在键盘【系统偏好设置 -> 键盘->快捷键->聚焦】修改 但是我不会去修改它&#xff0c;因为我不常用访达的搜索窗口&#xff0c;更多的是想快速打开访达文件夹窗口&#xff0c;可以通过第三方软件定义访达的快…

计算机视觉新巅峰,微软牛津联合提出MVSplat登顶3D重建

开篇&#xff1a;探索稀疏多视图图像的3D场景重建与新视角合成的挑战 3D场景重建和新视角合成是计算机视觉领域的一项基础挑战&#xff0c;尤其是当输入图像非常稀疏&#xff08;例如&#xff0c;只有两张&#xff09;时。尽管利用神经场景表示&#xff0c;例如场景表示网络&a…

鸿蒙OS开发教学:【编程之重器-装饰器】

HarmonyOS 有19种装饰器 必须【2】 绘制一个页面&#xff0c;这两个肯定会用到 EntryComponent 可选【17】 StatePropLinkObjectLinkWatchStylesStoragePropStorageLinkProvideConsumeObservedBuilderBuilderParamLocalStoragePropLocalStorageLinkExtendConcurrent 如果…

C# 排序的多种实现方式(经典)

一、 对数组进行排序 最常见的排序是对一个数组排序&#xff0c;比如&#xff1a; int[] aArray new int[8] { 18, 17, 21, 23, 11, 31, 27, 38 }; 1、利用冒泡排序进行排序&#xff1a; &#xff08;即每个值都和它后面的数值比较&#xff0c;每次拿出最小值&#xff09; s…

JavaEE初阶-线程3

文章目录 一、线程安全问题-内存可见性二、等待通知2.1 wait()方法2.2 notify()方法 一、线程安全问题-内存可见性 import java.util.Scanner;public class Demo27 {private static int count0;//下面这段代码会出现内存的可见性问题//将从内存中读取count值的操作称为load 判…

MySQL常见故障案例与优化介绍

前言 MySQL故障排查的意义在于及时识别并解决数据库系统中的问题&#xff0c;确保数据的完整性和可靠性&#xff1b;而性能优化则旨在提高数据库系统的效率和响应速度&#xff0c;从而提升用户体验和系统整体性能。这两方面的工作都对于保证数据库系统稳定运行、提升业务效率和…

总结:微信小程序中跨组件的通信、状态管理的方案

在微信小程序中实现跨组件通信和状态管理,有以下几种主要方案: 事件机制 通过事件机制可以实现父子组件、兄弟组件的通信。 示例: 父组件向子组件传递数据: 父组件: <child binddata"handleChildData" /> 子组件: Component({..., methods: { handleChildData(…

【经验分享】Ubuntu下如何解决问题arm-linux-gcc:未找到命令

【经验分享】Ubuntu下如何解决问题arm-linux-gcc&#xff1a;未找到命令 前言问题分析解决方法 前言 在编译过程中发现一个问题&#xff0c;明明之前安装了gcc-4.6版本&#xff0c;版本信息都是正常显示的&#xff0c;刚安装上去的时候也是可以用的。但不知道什么原因突然不能…

【LDLTS】拉普拉斯深能级瞬态光谱

Laplace deep level transient spectroscopy&#xff08;拉普拉斯深能级瞬态光谱&#xff0c;简称LDLTS&#xff09;是一种用于分析和表征半导体材料中深层能级缺陷的技术。它是传统能级瞬态光谱&#xff08;DLTS&#xff09;方法的扩展和改进&#xff0c;特别适用于解决传统DL…

穿山甲广告平台SDK接入效果怎么样?

广告收入是大多数开发者的应用变现收入来源&#xff0c;如何进行流流量变现是从应用设计之初就需要开发者思考的问题。 穿山甲广告平台作为国内第三方广告变现平台&#xff0c;是不少开发者选择的对接平台。 穿山甲广告平台的广告类型较多&#xff0c;有信息流&#xff0c;ba…

Chatgpt掘金之旅—有爱AI商业实战篇|文案写作|(三)

演示站点&#xff1a; https://ai.uaai.cn 对话模块 官方论坛&#xff1a; www.jingyuai.com 京娱AI 一、前言 人工智能&#xff08;AI&#xff09;技术作为当今科技创新的前沿领域&#xff0c;为创业者提供了广阔的机会和挑战。随着AI技术的快速发展和应用领域的不断拓展&…

如何借助Idea创建多模块的SpringBoot项目

目录 1.1、前言1.2、开发环境1.3、项目多模块结构1.4、新建父工程1.5、创建子模块1.6、编辑父工程的pom.xml文件 1.1、前言 springmvc项目&#xff0c;一般会把项目分成多个包:controler、service、dao、utl等&#xff0c;但是随着项目的复杂性提高&#xff0c;想复用其他一个模…

Linux系统-------------mysql主从复制和读写分离

目录 前言 为什么要主从复制&#xff1f; 主从复制谁复制谁&#xff1f; 数据放在什么地方&#xff1f; 一、mysql支持的复制类型 1.1STATEMENT&#xff1a;基于语句的复制 1.2ROW&#xff1a;基于行的复制 1.3MIXED&#xff1a;混合类型的复制 二、主从复制的工作过程 三个重…