Java泛型机制

news2025/2/21 21:51:59

每天学习一个知识点

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉
🍎个人主页:Leo的博客
💞当前专栏:每天一个知识点
✨特色专栏: MySQL学习
🥭本文内容:Java泛型机制
🖥️个人小站 :个人博客,欢迎大家访问
📚个人知识库: 知识库,欢迎大家访问

1. 为什么会有泛型

泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

引入泛型的意义在于:

  • 适用于多种数据类型执行相同的代码

我们通过一个例子来阐述,先看下下面的代码:

  • 泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型

    看下这个例子:

        private static int add(int a, int b) {
            System.out.println(a + "+"  + b + "=" + (a + b));
            return a + b;
        }
    
        private static float add(float a, float b) {
            System.out.println(a + "+" + b + "=" + (a + b));
            return a + b;
        }
    
        private static double add(double a, double b) {
            System.out.println(a + "+" + b + "=" + (a + b));
            return a + b;
        }
    

    如果没有泛型,要实现不同类型的加法,每种类型都需要重载一个add方法;通过泛型,我们可以复用为一个方法:

    private static <T extends Number> double add(T a, T b) {
        System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));
        return a.doubleValue() + b.doubleValue();
    }
    
    List list = new ArrayList();
    list.add("Leo");
    list.add(100d);
    list.add(new Person());
    

    我们在使用上述list中,list中的元素都是Object类型(无法约束其中的类型),所以在取出集合元素时需要人为的强制类型转化到具体的目标类型,且很容易出现java.lang.ClassCastException异常。

    引入泛型,它将提供类型的约束,提供编译前的检查:

    List<String> list = new ArrayList<String>();
    
    // list中只能放String, 不能放其它类型的元素
    

    如上代码所示,在没有泛型之前类型的检查类型的强转都必须由我们程序员自己负责,一旦我们犯了错(谁还能不犯错?),就是一个运行时崩溃等着我们。那时候我们就会抱怨了:***编译器,毛也检查不出来,我把一个Integer 类型的对象强行转换成String类型你在编译的时候也不告诉我,害的我程序运行时崩溃了,这个月奖金没了!

2. 泛型的作用对象

2.1 泛型集合

泛型本质上是提供类型的“类型参数”,也就是参数化类型。我们可以为类、接口或方法指定一个类型参数,通过这个参数限制操作的数据类型,从而保证类型转换的绝对安全。

例 1

下面将结合泛型与集合编写一个案例实现图书信息输出。

1)首先需要创建一个表示图书的实体类 Book,其中包括的图书信息有图书编号、图书名称和价格。Book 类的具体代码如下:

package com.Leo.generics;

/**
 * @author : Leo
 * @version 1.0
 * @date 2023/8/23 22:22
 * @description : Book
 */
public class Book {
    private int Id; // 图书编号
    private String Name; // 图书名称
    private int Price; // 图书价格

    public Book(int id, String name, int price) { // 构造方法
        this.Id = id;
        this.Name = name;
        this.Price = price;
    }

    public String toString() { // 重写 toString()方法
        return this.Id + ", " + this.Name + "," + this.Price;
    }
}

2)使用 Book 作为类型创建 Map 和 List 两个泛型集合,然后向集合中添加图书元素,最后输出集合中的内容。具体代码如下:

package com.Leo.generics;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author : Leo
 * @version 1.0
 * @date 2023/8/23 22:22
 * @description :
 */
public class Demo2 {
    public static void main(String[] args) {
        // 创建3个Book对象
        Book book1 = new Book(1, "灰姑娘", 8);
        Book book2 = new Book(2, "大猩猩", 12);
        Book book3 = new Book(3, "童话故事", 22);
        // 定义泛型 Map 集合
        Map<Integer, Book> books = new HashMap<>();
        // 将第一个 Book 对象存储到 Map 中
        books.put(1001, book1);
        // 将第二个 Book 对象存储到 Map 中
        books.put(1002, book2);
        // 将第三个 Book 对象存储到 Map 中
        books.put(1003, book3);
        System.out.println("泛型Map存储的图书信息如下:");
        for (Integer id : books.keySet()) {
            // 遍历键
            System.out.print(id + "——");
            // 不需要类型转换
            System.out.println(books.get(id));
        }
        // 定义泛型的 List 集合
        List<Book> bookList = new ArrayList<>();
        bookList.add(book1);
        bookList.add(book2);
        bookList.add(book3);
        System.out.println("泛型List存储的图书信息如下:");
        for (int i = 0; i < bookList.size(); i++) {
            // 这里不需要类型转换
            System.out.println(bookList.get(i));
        }
    }

}

在该示例中,第 21行代码创建了一个键类型为 Integer、值类型为 Book 的泛型集合,即指明了该 Map 集合中存放的键必须是 Integer 类型、值必须为 Book 类型,否则编译出错。在获取 Map 集合中的元素时,不需要将books.get(id);获取的值强制转换为 Book 类型,程序会隐式转换。在创建 List 集合时,同样使用了泛型,因此在获取集合中的元素时也不需要将bookList.get(i)代码强制转换为 Book 类型,程序会隐式转换。

执行结果如下:

image-20230823222645187

2.2 泛型类

除了可以定义泛型集合之外,还可以直接限定泛型类的类型参数。语法格式如下:

public class class_name<data_type1,data_type2,>{}

其中,class_name 表示类的名称,data_ type1 等表示类型参数。Java 泛型支持声明一个以上的类型参数,只需要将类型用逗号隔开即可。

泛型类一般用于类中的属性类型不确定的情况下。在声明属性时,使用下面的语句:

private data_type1 property_name1;private data_type2 property_name2;

该语句中的 data_type1 与类声明中的 data_type1 表示的是同一种数据类型。

在实例化泛型类时,需要指明泛型类中的类型参数,并赋予泛型类属性相应类型的值。例如,下面的示例代码创建了一个表示学生的泛型类,该类中包括 3 个属性,分别是姓名、年龄和性别。

package com.Leo.generics;

public class Student<N, A, S> {
    /** 姓名 */
    private N name;

    /** 年龄 */
    private A age;

    /**  性别 */
    private S sex;
    // 创建类的构造函数

    public Student(N name, A age, S sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    /** 下面是上面3个属性的setter/getter方法*/
    public N getName() {
        return name;
    }

    public void setName(N name) {
        this.name = name;
    }

    public A getAge() {
        return age;
    }

    public void setAge(A age) {
        this.age = age;
    }

    public S getSex() {
        return sex;
    }

    public void setSex(S sex) {
        this.sex = sex;
    }
}

接着创建测试类。在测试类中调用 Stu 类的构造方法实例化 Stu 对象,并给该类中的 3 个属性赋予初始值,最终需要输出学生信息。测试类的代码实现如下:

package com.Leo.generics;

/**
 * @author : Leo
 * @version 1.0
 * @date 2023/8/23 22:33
 * @description :
 */
public class Demo03 {
    public static void main(String[] args) {
        Student<String, Integer, Character> stu = new Student<>("Leo", 22, '男');
        String name = stu.getName();
        Integer age = stu.getAge();
        Character sex = stu.getSex();
        System.out.println("学生信息如下:");
        System.out.println("学生姓名:" + name + ",年龄:" + age + ",性别:" + sex);
    }
}

运行结果:

image-20230823223517060

在该程序的 Student 类中,定义了 3 个类型参数,分别使用 N、A 和 S 来代替,同时实现了这 3 个属性的 setter/getter 方法。在主类中,调用 Stu 类的构造函数创建了 Student 类的对象,同时指定 3 个类型参数,分别为 String、Integer 和 Character。在获取学生姓名、年龄和性别时,不需要类型转换,程序隐式地将 Object 类型的数据转换为相应的数据类型。

注意

    1. 泛型的类型参数只能是类类型,不能是简单类型。
    1. 不能对确切的泛型类型使用 instanceof 操作。如下面的操作是非法的,编译时会出错。

2.3 泛型方法

在此之前,我们所使用的泛型都是应用于整个类上。泛型同样可以在类中包含参数化的方法,而方法所在的类可以是泛型类,也可以不是泛型类。也就是说,是否拥有泛型方法,与其所在的类是不是泛型没有关系。

泛型方法使得该方法能够独立于类而产生变化。如果使用泛型方法可以取代类泛型化,那么就应该只使用泛型方法。另外,对一个 static 的方法而言,无法访问泛型类的类型参数。因此,如果 static 方法需要使用泛型能力,就必须使其成为泛型方法。

  • 自定义的标识符(T、V、E)来代表一个类型,用< >括住,放在方法返回值前面。可以被用到形参声明、方法返回值、方法定义中的变量声明和类型转换。
  • 泛型方法使得该泛型方法的类型参数独立于类而产生变化。泛型方法和泛型类没有关系。
  • 泛型方法的类型参数,一般情况下都是被推断inference出来。更具体地讲,只能被形参或返回值推断出来,当形参和返回值用了同一个类型参数时,二者推断出来的类型必须一样、或者符合多态。
  • 形参的类型参数通过实参确定;返回值的类型参数通过方法返回值赋值的对象确定。这也就是 类型参数推断
  • 当形参的类型参数和返回值的类型参数是同一个时,优先使用形参的推断。因为返回值的类型参数的推断是一种拖延行为。
  • 类的成员方法可以使用定义泛型类的类型参数(注意,这种方法不是泛型方法,只不过使用了类型参数而已);而类的静态方法不可以使用泛型类的类型参数,这是因为只有当创建泛型类对象时类型参数才会被具体类型确定,也就是说,泛型类的类型参数是与对象相关的。那么,很自然地,作为一个static方法肯定不可以使用泛型类的类型参数。 static方法想用到泛型只能将其定义为泛型方法。

定义泛型方法的语法格式如下:

[访问权限修饰符] [static] [final] <类型参数列表> 返回值类型 方法名([形式参数列表])

例如:

public static <T> List find(Class<T> cs,int userId){}

一般来说编写 Java 泛型方法,其返回值类型至少有一个参数类型应该是泛型,而且类型应该是一致的,如果只有返回值类型或参数类型之一使用了泛型,那么这个泛型方法的使用就被限制了。下面就来定义一个泛型方法,具体介绍泛型方法的创建和使用。

public static <T> void List(T book) { // 定义泛型方法
        if (book != null) {
            System.out.println(book);
        }
    }
    public static void main(String[] args) {
        Book stu = new Book(1, "Leo学Java", 28);
        List(stu);
        // 调用泛型方法
    }

2.4 泛型数组

其实在很多文档中都有提到这个概念,于是我翻阅了Sun文档,经过查看Sun的说明文档,在java中是**”不能创建一个确切的泛型类型的数组”**的。

也就是说下面的这个例子是不可以的:

List<String>[] ls = new ArrayList<String>[10];  

而使用通配符创建泛型数组是可以的,如下面这个例子:

List<?>[] ls = new ArrayList<?>[10];  

这样也是可以的:

List<String>[] ls = new ArrayList[10];

3. 泛型通配符

我们在定义泛型类,泛型方法,泛型接口的时候经常会碰见很多不同的通配符,比如 T,E,K,V 等等,这些通配符又都是什么意思呢?

1. 常用的 T,E,K,V,?

本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,? 是这样约定的:

  • ? 表示不确定的 java 类型
  • T (type) 表示具体的一个java类型
  • K V (key value) 分别代表java键值中的Key Value
  • E (element) 代表Element

2. ? 和 T 的区别

image-20230830154957129

?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 ? 不行,比如如下这种 :

// 可以
T t = operate();

// 不可以
? car = operate();

简单总结下:

T 是一个 确定的 类型,通常用于泛型类和泛型方法的定义,?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。

4. 总结

本文零碎整理了下 JAVA 泛型中的一些点,不是很全,仅供参考。如果文中有不当的地方,欢迎指正。

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

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

相关文章

el-table表尾添加合计行,自动合计,且特殊列自定义计算展示

效果如图 1.element-ui的table表格有合计功能&#xff0c;但是功能却不完善&#xff0c;会有不显示和计算出现错误的问题&#xff0c;项目中有遇到&#xff0c;所以记录下 show-summary&#xff1a;自动合计 getSummaries&#xff08;&#xff09;&#xff1a;对合计行进行特…

Unity 粒子特效遮罩(ParticleMask)

1.需求&#xff1a; 游戏中粒子特效能实现非常好的效果&#xff0c;但是由于粒子特效是独立的系统&#xff0c;Unity自带的Mask普通的遮罩&#xff0c;遮不住粒子特效。 2.实现原理&#xff1a; 通过shader把超出范围的粒子纹理(Texture)&#xff0c;改成透明颜色&#xff0…

CS144(2023 Spring)Lab 1: stitching substrings into a byte stream

文章目录 前言其他笔记相关链接 1. Getting started2. Putting substrings in sequence2.1 需求分析2.2 注意事项2.3 代码实现 3. 测试与优化 前言 这一个Lab主要是实现一个TCP receiver的字符串接收重组部分。 其他笔记 Lab 0: networking warmup Lab 1: stitching substri…

【Datawhale】AI夏令营第三期——基于论文摘要的文本分类笔记(上)

暑期参加了Datawhale的第三期AI夏令营&#xff0c;学习的是NLP方向&#xff0c;在此期间&#xff0c;我们通过比赛打榜的形式进行NLP的学习。今天&#xff0c;主要分享和记录一下这一期夏令营的学习历程和笔记。 文章目录 赛题背景赛题任务赛题数据集评价指标解题思路任务一&am…

C++之“00000001“和“\x00\x00\x00\x01“用法区别(一百八十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

NTP时钟同步服务器

目录 一、什么是NTP&#xff1f; 二、计算机时间分类 三、NTP如何工作&#xff1f; 四、NTP时钟同步方式&#xff08;linux&#xff09; 五、时间同步实现软件&#xff08;既是客户端软件也是服务端软件&#xff09; 六、chrony时钟同步软件介绍 七、/etc/chrony.conf配置文件介…

75 # koa 基本逻辑实现以及属性的扩展

准备工作 新建自己的 kaimo-koa 文件夹&#xff0c;结构如下&#xff1a; lib application.js&#xff1a;创建应用context.js&#xff1a;上下文request.js&#xff1a;koa 中自己实现的 request 的对象response.js&#xff1a;koa 中自己实现的 response 的对象 package.js…

HTTPS安全通信和SSL Pinning

随着互联网的迅速发展&#xff0c;网络通信安全问题日益凸显。在这一背景下&#xff0c;HTTPS作为一种加密通信协议得到了广泛应用&#xff0c;以保障用户的数据隐私和信息安全。本文将介绍HTTPS的基本原理、发展历程&#xff0c;以及与之相关的中间人攻击和防护方法。 1. HTT…

MySQL基础入门

推荐查看 数据库相关概念 MySQL百度百科 MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB 公司开发&#xff0c;属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一&#xff0c;在 WEB 应用方面&#xff0c;MySQL是最好的 RDBMS (Relational Databa…

爬虫逆向实战(二十八)--某税网第一步登录

一、数据接口分析 主页地址&#xff1a;某税网 1、抓包 通过抓包可以发现登录接口是factorAccountLogin 2、判断是否有加密参数 请求参数是否加密&#xff1f; 通过查看载荷模块可以发现有一个datagram 和 一个signature加密参数 请求头是否加密&#xff1f; 通过查看“标…

【Flutter】下载安装Flutter并使用学习dart语言

前言 安装flutter, 并使用flutter内置的dartSDK学习使用dart语言。 编辑器&#xff1a; Android Studio fluuter 版本 : flutter_windows_3.13.1 内置dartSDK : 3.1.0 dart路径路径&#xff1a; flutter安装路径\bin\cache\dart-sdk 安装Flutter 下载安装包 flutter下载地址…

QT 界面相关操作

1> 创建自定义类时需要指定父类 2> 第一个界面的相关操作 #include "widget.h" #include<iostream> //printf #include<QDebug> //qDebuf #include<QIcon> //图标的头文件 using namespace std; //coutWidget::Widget(QWidget *…

【注册岩土】Python土力学与基础工程计算.PDF-摩尔-库伦强度理论

8.3 Python求解 Python求解代码如下&#xff1a; 1.import math 2. 3.sigma1 300 # 最大主应力&#xff0c;单位 kPa 4.sigma3 100 # 最小主应力&#xff0c;单位 kPa 5.alpha 30 # m-n面与最小主应力方向夹角&#xff0c;单位度 6. 7.rad_alph…

uniapp的 picker 日期时间选择器

效果图&#xff1a; dateTimePicker.js function withData(param){return param < 10 ? 0 param : param; } function getLoopArray(start,end){var start start || 0;var end end || 1;var array [];for (var i start; i < end; i) {array.push(withData(i))…

C++网狐服务器引入开源日志库

很多人对日志库不以为然&#xff0c;包括网狐这种十几年的公司都不重视&#xff0c;其实日志库记录的东西能在线上出问题时高效解决&#xff0c;特别是别人写的东西&#xff0c;人又走了&#xff0c;出了问题&#xff0c;还可以用日志分析快速解决。要是没有日志记录&#xff0…

【kubernetes系列】Calico原理及配置

概述 Calico是针对容器&#xff0c;虚拟机和基于主机的本机工作负载的开源网络和网络安全解决方案。 Calico支持广泛的平台&#xff0c;包括Kubernetes&#xff0c;OpenShift&#xff0c;Docker EE&#xff0c;OpenStack和裸机服务。 Calico在每个计算节点都利用Linux Kernel实…

TiDB Serverless Branching:通过数据库分支简化应用开发流程

2023 年 7 月 10 日&#xff0c;TiDB Serverless 正式商用。这是一个完全托管的数据库服务平台&#xff08;DBaaS&#xff09;&#xff0c;提供灵活的集群配置和基于用量的付费模式。紧随其后&#xff0c;TiDB Serverless Branching 的测试版也发布了。 TiDB Serverless Branc…

【二等奖方案】大规模金融图数据中异常风险行为模式挖掘赛题「冀科数字」解题思路

第十届CCF大数据与计算智能大赛&#xff08;2022 CCF BDCI&#xff09;已圆满结束&#xff0c;大赛官方竞赛平台DataFountain&#xff08;简称DF平台&#xff09;正在陆续释出各赛题获奖队伍的方案思路&#xff0c;欢迎广大数据科学家交流讨论。 本方案为【大规模金融图数据中…

Java代码审计15之Apache log4j2漏洞

文章目录 1、log4j简介2、复现2.1、高版本测试2.2、测试代码2.3、补充之dns探测2.3.1、rmi、ldap也可以dnslog探测 2.3.2、dnslog外带信息 3、漏洞原理3.1、漏洞的危害大的背景3.2、具体的代码调试 4、靶场测试4.1、dns探测4.2、工具下载与使用4.3、测试 5、bypass 1、log4j简介…

FBX SDK 开发环境配置 visual studio 2022

FBX | Adaptable File Formats for 3D Animation Software | Autodesk. 下载windows的sdk并安装. 创建一个c console 工程 设置include目录 添加预处理宏 FBX_SHARED1 添加fbx sdk lib 目录 添加依赖lib : libfbxsdk-md.lib libxml2-md.lib zlib-md.lib 配置完毕.