Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >第08天Java泛型机制

第08天Java泛型机制

作者头像
程序员Leo
发布于 2023-09-09 07:40:01
发布于 2023-09-09 07:40:01
21000
代码可运行
举报
文章被收录于专栏:Java知识点Java知识点
运行总次数:0
代码可运行

# 1. 为什么会有泛型

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

引入泛型的意义在于:

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

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

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

看下这个例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 方法;通过泛型,我们可以复用为一个方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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();
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List list = new ArrayList();
list.add("Leo");
list.add(100d);
list.add(new Person());

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<String> list = new ArrayList<String>();

// list中只能放String, 不能放其它类型的元素

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

# 2. 泛型的作用对象

# 2.1 泛型集合

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

# 例 1

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 两个泛型集合,然后向集合中添加图书元素,最后输出集合中的内容。具体代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 类型,程序会隐式转换。

执行结果如下:

# 2.2 泛型类

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class class_name<data_type1,data_type2,>{}

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private data_type1 property_name1;private data_type2 property_name2;

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 个属性赋予初始值,最终需要输出学生信息。测试类的代码实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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);
    }
}

运行结果:

在该程序的 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 方法想用到泛型只能将其定义为泛型方法。

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[访问权限修饰符] [static] [final] <类型参数列表> 返回值类型 方法名([形式参数列表])

例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static <T> List find(Class<T> cs,int userId){}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 中是 **” 不能创建一个确切的泛型类型的数组”** 的。

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<String>[] ls = new ArrayList<String>[10];  

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<?>[] ls = new ArrayList<?>[10];  

这样也是可以的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 的区别

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 可以
T t = operate();

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

简单总结下:

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

# 4. 总结

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-09-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java进阶-集合(3)与泛型
这次介绍集合中的Iterator迭代器,以及泛型。简单来说,泛型对集合的元素类型进行了限制,使用泛型可以在编译时检查类型安全,提高代码的重用率。内容如下
reload
2024/03/01
3350
Java进阶-集合(3)与泛型
Java泛型是什么?
工作已有五年之久,回望过去,没有在一线城市快节奏下学习成长,只能自己不断在工作中学习进步,最近一直想写写属于自己的文章,记录学习的内容和知识点,当做一次成长。
科技新语
2024/10/28
1720
Java泛型是什么?
Java知识点总结之Java泛型
作者:苏生 链接: https://segmentfault.com/a/1190000014824002 泛型 泛型就是参数化类型 适用于多种数据类型执行相同的代码 泛型中的类型在使用时指定 泛型归根到底就是“模版” 优点:使用泛型时,在实际使用之前类型就已经确定了,不需要强制类型转换。 泛型主要使用在集合中 import java.util.ArrayList; import java.util.List; public class Demo01 { // 不使用泛型,存取数据麻烦
用户1257393
2018/07/30
5730
Java泛型总结
集合容器类“设计阶段/声明阶段”不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为 Object,JDK1.5 之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection<E>,List<E>,ArrayList<E>这个 <E> 就是类型参数,即泛型。
乐心湖
2021/01/18
8660
Java泛型总结
了解 Java泛型
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
阿珍
2025/03/03
410
了解 Java泛型
Java泛型
使用Object类型作为引用,由于是Object类型,所以说并不能直接判断存储的类型到底是String还是Integer,取值只能进行强制类型转换,显然无法在编译期确定类型是否安全
用户9645905
2023/10/23
1850
Java基础系列2:Java泛型
该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每个Java知识点背后的实现原理,更完整地了解整个Java技术体系,形成自己的知识框架。
说故事的五公子
2019/11/10
5610
面试系列之-JAVA泛型剖析(JAVA基础)
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参列表,普通方法的形参列表中,每个形参的数据类型是确定的,而变量是一个参数。在调用普通方法时需要传入对应形参数据类型的变量(实参),若传入的实参与形参定义的数据类型不匹配,则会报错。
用户4283147
2023/08/21
4350
面试系列之-JAVA泛型剖析(JAVA基础)
Java泛型详解,史上最全图文详解「建议收藏」
毫不夸张的说,泛型是通用设计上必不可少的元素,所以真正理解与正确使用泛型,是一门必修课。
全栈程序员站长
2022/09/08
9610
【Java】泛型
🍊 泛型(Generics)是Java编程语言中的一个强大的特性,它提供了 编译时类型安全检测机制,这意味着可以在编译期间检测到非法的类型。泛型的使用减少了程序中的强制类型转换和运行时错误的可能性。
IsLand1314
2024/10/21
1500
【Java】泛型
最详细的java泛型详解
作者:VieLei 来源:blog.csdn.net/s10461 对java的泛型特性的了解仅限于表面的浅浅一层,直到在学习设计模式时发现有不了解的用法,才想起详细的记录一下。 本文参考java 泛型详解、Java中的泛型方法、 java泛型详解 1. 概述 泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。 什么是泛型?为什么要使用泛型? 泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是
Tanyboye
2018/07/02
6840
Java泛型总结
泛型是jdk5引入的类型机制,就是将类型参数化,它是早在1999年就制定的jsr14的实现。
pollyduan
2019/11/04
1K0
初识Java泛型
https://blog.csdn.net/weixin_44510615/article/details/102718400
润森
2019/10/30
4020
Java泛型详解
根据给出的文章内容,撰写摘要总结。
Java后端工程师
2017/12/16
1.8K0
Java从入门到精通九(Java泛型)
泛型是什么?我们在哪里会遇到? 比如在一些集合类里面,我们可以看到对于键值的参数化限制。作用就是指定了键值的类型。
兰舟千帆
2022/07/16
6950
Java从入门到精通九(Java泛型)
Java泛型全解析
把一个对象放进集合中之后,集合就会忘记这个对象的数据类型,当再次取出该对象时,该对象的编译类型就变成Object类型了
迹_Jason
2019/05/28
6540
Java 基础(一)| 使用泛型的正确姿势
为跳槽面试做准备,今天开始进入 Java 基础的复习。希望基础不好的同学看完这篇文章,能掌握泛型,而基础好的同学权当复习,希望看完这篇文章能够起一点你的青涩记忆。
JavaFish
2020/01/14
5940
Java 基础(一)| 使用泛型的正确姿势
Java基础11:Java泛型详解
这是一位阿里 Java 工程师的技术小站,作者黄小斜,专注 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点Docker、ELK,同时也分享技术干货和学习经验,致力于Java全栈开发!(关注公众号后回复”资料“即可领取 3T 免费技术学习资源以及我我原创的程序员校招指南、Java学习指南等资源)
程序员黄小斜
2019/04/07
5050
Java泛型
1.java泛型及就是在jdk1.5之后出现的一个新的安全机制 我们发现在集合框架中我们可以放入任何的元素,然而这样做并没有任何意义,绝大多时候我们是默认我们 知道这个容器需要存放什么样的内容,但是用户的输入是不安全的如果他们输入了各种类型然后我们只对某些类型 进行了处理显然到时候运行时必然报错 所以为了解决这个问题,类似于数组的解决方式给集合限定了类型使用尖括号来限定,当然包括Iterator 他的好处就是安全 2.comparable接口和comparator都可以使用泛型,在使用
lwen
2018/04/17
1.5K0
细说 Java 泛型及其应用
当获取列表中的第二个元素时,会报错,java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String。这是常见的类型转换错误。
aoho求索
2019/05/07
7240
相关推荐
Java进阶-集合(3)与泛型
更多 >
LV.1
能源公司Java开发工程师
作者相关精选
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验