泪伤荡的编程指南 泪伤荡的编程指南
首页
  • 基础篇
  • 集合篇
  • 并发篇
  • JVM篇
  • 新特性
  • 进阶篇
  • 网络
  • 操作系统
  • 数据结构与算法
  • 硬件
  • 基础篇
  • MySql
  • Oracle
  • PostgreSQL
  • 达梦
  • Redis
  • Mongodb
  • Hive
  • 数据库比较
  • Spring
  • SpringMvc
  • SpringBoot
  • Hibernate
  • iBatis
  • Mybatis
  • Mybatis-plus
  • Mybatis-plus-join
  • 各个框架对比
  • UML画图
  • 设计须知
  • 开发流程
  • 开发理论
  • 架构体系
  • 设计模式
  • 开源知识
  • 分布式解决方案
  • SpringCloud
  • API网关
  • 注册中心
  • 配置中心
  • 服务调用
  • 分布式事务
  • 消息队列
  • 调度作业
  • 链路追踪
  • 服务保障
  • 搜索引擎Elk
  • 安全框架
  • 监控体系
  • 部署容器
  • Netty
  • Tomcat
  • Nginx
  • 图片云存储
  • 云存储
  • 虚拟机Linux
  • 项目部署
  • 容器部署
  • 开发工具篇
  • 工具库篇
  • 开发技巧篇
  • 工具类系列
  • Bug记录仓库
  • 随笔
  • HTML与CSS
  • JS学习
  • Vue3入门
  • Vue3进阶
  • 黑马Vue3
  • 视频网站
  • 音乐网站
  • 商城网站
  • 论坛网站
  • scrm项目
  • Yudao-cloud
  • RuoYi-Vu-cloud
  • 博客搭建
  • 网站收藏箱
  • 断墨寻径摘录
  • 费曼学习法
  • Java术语
  • 命名英语
  • 业务英语
  • 表字段英语
  • 包名英语
Github (opens new window)
首页
  • 基础篇
  • 集合篇
  • 并发篇
  • JVM篇
  • 新特性
  • 进阶篇
  • 网络
  • 操作系统
  • 数据结构与算法
  • 硬件
  • 基础篇
  • MySql
  • Oracle
  • PostgreSQL
  • 达梦
  • Redis
  • Mongodb
  • Hive
  • 数据库比较
  • Spring
  • SpringMvc
  • SpringBoot
  • Hibernate
  • iBatis
  • Mybatis
  • Mybatis-plus
  • Mybatis-plus-join
  • 各个框架对比
  • UML画图
  • 设计须知
  • 开发流程
  • 开发理论
  • 架构体系
  • 设计模式
  • 开源知识
  • 分布式解决方案
  • SpringCloud
  • API网关
  • 注册中心
  • 配置中心
  • 服务调用
  • 分布式事务
  • 消息队列
  • 调度作业
  • 链路追踪
  • 服务保障
  • 搜索引擎Elk
  • 安全框架
  • 监控体系
  • 部署容器
  • Netty
  • Tomcat
  • Nginx
  • 图片云存储
  • 云存储
  • 虚拟机Linux
  • 项目部署
  • 容器部署
  • 开发工具篇
  • 工具库篇
  • 开发技巧篇
  • 工具类系列
  • Bug记录仓库
  • 随笔
  • HTML与CSS
  • JS学习
  • Vue3入门
  • Vue3进阶
  • 黑马Vue3
  • 视频网站
  • 音乐网站
  • 商城网站
  • 论坛网站
  • scrm项目
  • Yudao-cloud
  • RuoYi-Vu-cloud
  • 博客搭建
  • 网站收藏箱
  • 断墨寻径摘录
  • 费曼学习法
  • Java术语
  • 命名英语
  • 业务英语
  • 表字段英语
  • 包名英语
Github (opens new window)
  • 基础篇

    • JavaSE - 初始 Java
    • JavaSE - 注释
    • JavaSE - 标识符
    • JavaSE - 数据类型
    • JavaSE - 类型转换
    • JavaSE - 变量和常量
    • JavaSE - 运算符
    • JavaSE - 包机制
    • JavaSE - JavaDoc
    • JavaSE - 方法
    • JavaSE - 用户交互 Scanner
    • JavaSE - 顺序结构
    • JavaSE - 选择结构
    • JavaSE - 循环结构
    • JavaSE - Break 和 Continue
    • JavaSE - 数组
    • JavaSE - 异常机制
    • JavaSE - 面向对象
    • JavaSE - 常用类
    • JavaSE - IO流
    • 思考 - 如何快速掌握一门语言
  • 集合篇

    • Java集合概述
    • List接口
    • Set接口
    • Queue接口
    • Map接口
  • 并发篇

    • Java并发基础小结
    • 锁详解
    • Synchronized和Volatile的使用与区别
    • 线程池详解
    • CompletableFuture学习
    • Java内存管理总结
  • JVM篇

    • JVM基础入门
    • JVM常问
  • 新特性

    • JDK5

      • 网站内容
      • 手动填充
    • JDK8

      • Java8 新特性
      • JDK新特性
    • JDK11

      • 网站内容
      • 手动填充
    • JDK17

      • 网站内容
      • 手动填充
    • JDK21

      • 网站内容
      • 手动填充
    • JDK25

      • 网站内容
      • 手动填充
  • 进阶篇

    • Java程序设计概述
    • Java程序设计环境
    • Java的基本程序设计结构
    • 对象与类
    • 继承
    • 接口、lambda表达式与内部类
    • 异常、断言和日志
    • 泛型程序设计
    • 集合
    • 图形用户界面程序设计
    • Swing用户界面组件
    • 并发
    • 附录-Java关键字
    • Java泛型核心知识总结
      • 泛型
        • 什么是泛型?有什么用?
        • 泛型有哪些限制?为什么?
        • 项目中哪里用到了泛型?
        • 什么是类型擦除?
        • 什么是桥方法?
      • 通配符
        • 什么是通配符?有什么作用?
        • 通配符 ? 和常用的泛型 T 之间有什么区别?
        • 介绍一下常用的通配符?
      • 参考
    • Java反射详解
    • Java SPI机制详解
  • Java
  • 进阶篇
泪伤荡
2023-06-28
目录

Java泛型核心知识总结

# Java 泛型核心知识总结

# 泛型

# 什么是泛型?有什么用?

Java 泛型(Generics)是 JDK 5 中引入的一个新特性,它提供了一种类型安全的编程机制,可以在编译时检查类型错误,避免了在运行时出现类型转换异常的情况。它可以使程序员在编写代码时指定类型参数,从而使得代码更加灵活和可重用。

比如 ArrayList<Persion> persons = new ArrayListPersion>() 这行代码就指明了该 ArrayList 对象只能传入 Persion 对象,如果传入其他类型的对象就会报错。

类型参数 T 是一种占位符类型,用于表示实际的类型。

一般在哪定义?

Java 泛型可以用于类、接口和方法的定义中。

三种使用方式:泛型类、泛型接口、泛型方法。

使用泛型有什么好处?

简单来说,使用泛型参数,可以增强代码的可读性以及稳定性。

使用泛型可以带来许多好处,比如:

  • 类型安全:Java 泛型可以在编译时检查类型错误,避免了在运行时出现类型转换异常的情况。
  • 代码重用:泛型可以使代码更加通用和模块化,可以重用在不同的场景中。
  • 更好的性能:泛型可以避免不必要的类型转换和装箱操作,从而提高代码的性能。

泛型的实现方式?

泛型主要通过以下两种方式来实现:参数化类型和通配符类型。

# 泛型有哪些限制?为什么?

  1. 泛型参数 <T> 不能是基本类型,例如 int,因为实际类型是 Object,Object 类型无法持有基本类型。
  2. 无法取得带泛型的 Class。(getClass())
  3. 无法判断带泛型的类型。因为擦除后为 object 类型,object 无法使用 > 进行比较。
  4. 不能实例化泛型参数的数组。因为擦除后为 object 后无法进行类型判断。
  5. 不能实现两个不同泛型参数的同一接口,擦除后多个父类的桥方法将冲突。
  6. 不能使用 static 修饰泛型变量。
  7. 泛型数组的限制:Java 泛型数组的创建和使用受到一些限制,例如无法创建泛型数组、无法向泛型数组中添加元素等。

# 项目中哪里用到了泛型?

比如:

  • 自定义接口通用返回结果类 CommonResult<T> 通过参数 T 可根据具体的返回类型动态指定结果的数据类型。
  • 定义 Excel 处理类 ExcelUtil<T> 用于动态指定 Excel 导出的数据类型。
  • 构建集合工具类(参考 Collections 中的 sort, binarySearch 方法)。

# 什么是类型擦除?

类型擦除是指在 Java 编译器将泛型代码编译成字节码时,会将泛型类型擦除,替换为实际的类型或者 Object 类型,从而使得泛型类型在运行时不存在。

注意:是【实际的类型】或者 【Object】类型。

为什么要擦除?

这是因为 Java 虚拟机并不支持泛型,所以需要在编译期对泛型进行擦除,将泛型代码转换为普通的 Java 类型。

比如:

泛型类型没有定义类型参数的限定类型的情况:

class MyList<T> { ... }
1

对于上面的 MyList<T> 类,实际上编译器会将其擦除成如下形式:

class MyList {
    ...
}
1
2
3

在运行时,我们无法获取泛型类型的类型参数,例如 无法获取 MyList<String> 和 MyList<Integer> 的类型参数,它们都被擦除为 MyList 类型。

# 什么是桥方法?

桥方法(Bridge Method)是 Java 泛型类型擦除机制的一种补偿措施。它是指在泛型类或泛型接口中,由编译器自动生成的一个方法,用于在类型擦除后保持多态性。

具体说明:

在 Java 泛型中,由于类型擦除机制的存在,导致在某些情况下,泛型类型的继承关系会被破坏。

例如下面的代码:

public class MyList<T> {
    ...
    public void add(T element) {
        ...
    }
}
1
2
3
4
5
6

假设我们定义了一个子类:

public class MyStringList extends MyList<String> {
    ...
}
1
2
3

由于类型擦除机制的存在,MyStringList 类实际上是继承自 MyList 类的原始类型,而不是继承自 MyList<String> 类型。

因此,如果我们在 MyStringList 类中定义一个重写 add() 方法的话,会出现编译错误:

public class MyStringList extends MyList<String> {
    ...
    @Override
    public void add(String element) {
        ...
    }
}
1
2
3
4
5
6
7

这是因为 Java 编译器会将 MyStringList 类中的 add() 方法擦除成如下形式:

public void add(Object element) {
    ...
}
1
2
3

这个方法的参数类型是 Object,与 MyList<String> 中的 add 方法的参数类型不同,因此编译器会报错。

为了解决这个问题,Java 编译器会在 MyStringList 类中自动生成一个桥方法,用于在类型擦除后保持多态性:

public class MyStringList extends MyList<String> {
    ...
    @Override
    public void add(Object element) {
        add((String) element);
    }
    public void add(String element) {
        ...
    }
}
1
2
3
4
5
6
7
8
9
10

在上面的代码中,编译器会自动生成一个桥方法 add(Object element),它会调用原始方法 add(String element),从而保持多态性。

# 通配符

# 什么是通配符?有什么作用?

泛型类型是固定的,某些场景下使用起来不太灵活,于是,通配符就来了!

通配符可以允许类型参数变化,用来解决泛型无法协变的问题。

举个例子:

// 限制类型为 Person 的子类
<? extends Person>
// 限制类型为 Manager 的父类3
<? super Manager>
1
2
3
4

# 通配符 ? 和常用的泛型 T 之间有什么区别?

  1. T 可以用于声明变量或常量,而 ? 不行。
  2. T 一般用于声明泛型类或方法,通配符 ? 一般用于泛型方法的调用代码和形参。
  3. T 在编译期会被擦除为限定(实际)类型或 object,通配符用于捕获具体类型。

擦除为限定(实际)类型是什么意思?

1、如果泛型类型定义了类型参数的限定类型,例如:

class MyList<T extends Number> {
    private T[] elements;
    public void add(T element) { ... }
    public T get(int index) { ... }
}
1
2
3
4
5

那么在编译时,编译器会将类型参数 T 擦除为其限定类型 Number,例如:

class MyList {
    private Number[] elements;
    public void add(Number element) { ... }
    public Number get(int index) { ... }
}
1
2
3
4
5

因此,在运行时,无法获取泛型类型的类型参数 T,而只能获取其限定类型 Number。

2、如果泛型类型没有定义类型参数的限定类型,例如:

class MyList<T> {
    private T[] elements;
    public void add(T element) { ... }
    public T get(int index) { ... }
}
1
2
3
4
5

那么在编译时,编译器会将类型参数 T 擦除为 Object 类型,例如:

class MyList {
    private Object[] elements;
    public void add(Object element) { ... }
    public Object get(int index) { ... }
}
1
2
3
4
5

因此,在运行时,无法获取泛型类型的类型参数 T,而只能获取其擦除后的类型 Object。

# 介绍一下常用的通配符?

常用的通配符有三种:

  1. <? extends T> :上边界通配符 extends,表示该泛型必须是 T 的子类(包括 T 本身),用于限定泛型的上界。
  2. <? super Integer>:下边界通配符 super,表示该泛型必须是 T 的父类(包括 T 本身),用于限定泛型的下界。
  3. <?>:无限定通配符 ?,表示任意类型,用于表示不确定的类型参数。无限定通配符 <?> 很少使用,可以用 <T> 替换,同时它是所有 <T> 类型的父类。

# 参考

一文搞懂泛型:https://www.cnblogs.com/XiiX/p/14719568.html (opens new window)

上次更新: 2024/12/7 02:33:56
附录-Java关键字
Java反射详解

← 附录-Java关键字 Java反射详解→

Theme by Vdoing | Copyright © 2024-2025 泪伤荡 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式