Java中对泛型的理解

一、泛型是什么?

1. 定义:
泛型允许你在定义类、接口或方法时使用类型参数(Type Parameter)。在使用时(如声明变量、创建实例时),再用具体的类型实参(Type Argument) 替换这个参数。它就像是方法的形参和实参,但操作的对象是类型本身。

2. 核心目的:

  • 类型安全(Type Safety):在编译期就能检查类型是否正确,将运行时错误(ClassCastException)转变为编译期错误。

    // 没有泛型:编译通过,运行时报 ClassCastException
    List list = new ArrayList();
    list.add("Hello");
    Integer num = (Integer) list.get(0); // 运行时错误!// 有泛型:编译期直接报错,无法通过编译
    List<String> list = new ArrayList<>();
    list.add("Hello");
    Integer num = list.get(0); // 编译错误:不兼容的类型
  • 消除强制类型转换(Eliminate Casts):代码更简洁、清晰。

    // 没有泛型
    String str = (String) list.get(0);// 有泛型
    String str = list.get(0); // 自动知道是String,无需强转

二、泛型擦除(Type Erasure)—— 泛型的实现原理

这是 Java 泛型的核心机制,也是很多限制的根源。

1. 是什么?
Java 的泛型是在编译器层面实现的,而不是在运行时。在编译后,所有的泛型类型信息都会被移除(擦除)。编译器在生成字节码时:

  • 将泛型类型参数替换为它的边界(Bound)(如 T extends Number 则替换为 Number)。

  • 如果类型参数是无边界的(如 <T>),则替换为 Object

  • 随之插入必要的强制类型转换,以保持类型安全。

2. 例子:

// 源代码(编译前)
public class Box<T> {private T value;public void set(T value) { this.value = value; }public T get() { return value; }
}Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String value = stringBox.get(); // 无需强转// 编译后(概念上,字节码级别)
public class Box { // T 被擦除private Object value; // T 被替换为 Objectpublic void set(Object value) { this.value = value; }public Object get() { return value; } // 返回Object
}Box stringBox = new Box(); //  raw type
stringBox.set("Hello");
String value = (String) stringBox.get(); // 编译器插入了强转!

3. 带来的影响与限制:

  • 不能使用基本类型:如 List<int> 是错误的,必须用 List<Integer>。因为擦除后是 Object,而 Object 不能持有 int

  • instanceof 和 getClass():运行时无法检测泛型类型。

    List<String> list = new ArrayList<>();
    System.out.println(list instanceof List<String>); // 编译错误
    System.out.println(list instanceof List); // 正确,但只能检查到是List,不是List<String>
  • 不能创建泛型数组new T[] 或 new List<String>[] 都是错误的。因为数组需要在运行时知道其确切的元素类型来保证类型安全,而擦除破坏了这个机制。

  • 不能实例化类型参数new T() 是错误的,因为擦除后是 new Object(),这通常不是你想要的。


三、桥接方法(Bridge Method)—— 保护多态

桥接方法是编译器为了解决类型擦除多态冲突而自动生成的方法。

场景: 当一个类继承或实现了一个泛型类/接口,并重写了其中的泛型方法时。

例子:

// 泛型接口
public interface Comparable<T> {int compareTo(T other);
}// 实现类
public class String implements Comparable<String> {// 我们重写的方法签名:int compareTo(String other)@Overridepublic int compareTo(String other) { ... }
}

由于类型擦除,父接口 Comparable 中的方法在字节码层面变成了 int compareTo(Object other)。这导致子类 String 实际上有两个方法:

  1. int compareTo(String other) (我们自己写的)

  2. int compareTo(Object other) (编译器生成的桥接方法

桥接方法内部做了什么?

// 编译器生成的桥接方法(概念上)
public int compareTo(Object other) {// 在桥接方法中,进行类型检查和安全地向下转型return compareTo((String) other); // 调用我们重写的那个具体类型的方法
}

桥接方法确保了即使在类型擦除后,Java的多态机制(父类引用调用子类方法)也能正常工作,同时保证了类型安全。


四、泛型继承和通配符(Wildcards):extends & super

这是泛型中最难理解但最强大的部分,通常用 PECS(Producer-Extends, Consumer-Super) 原则来概括。

1. 泛型不变性(Invariance)
首先,理解这一点至关重要:Box<String> 和 Box<Object> 没有继承关系,即使 String 是 Object 的子类。

Box<Object> box = new Box<String>(); // 编译错误!不兼容的类型

这种特性称为不变性(Invariance)。它保证了类型安全。如果上面成立,你就可以 box.set(new Integer(100)),从而把一个 Integer 放进一个声明为 String 的盒子里。

2. 通配符 ?
为了解决需要泛型协变的需求,引入了通配符 ?

3. 上界通配符 ? extends T (Producer)

  • 含义:表示“未知的某种类型,但它是 T 或 T 的子类”。

  • 用途:当你主要从泛型结构中读取数据(Producer) 时使用。

  • 规则:你可以安全地从中读取(赋值给 T 或父类引用),但不能向其写入(除了 null)。因为编译器不知道具体是哪种子类,写入可能破坏类型安全。

    List<? extends Number> numbers = new ArrayList<Integer>(); // 协变,允许
    Number num = numbers.get(0); // OK, 可以读取为Number
    numbers.add(new Integer(100)); // 编译错误!不能写入

4. 下界通配符 ? super T (Consumer)

  • 含义:表示“未知的某种类型,但它是 T 或 T 的父类”。

  • 用途:当你主要向泛型结构中写入数据(Consumer) 时使用。

  • 规则:你可以安全地向其写入 T 或 T 的子类对象,但读取出来只能赋值给 Object 引用。因为编译器只知道容器里是 T 的父类,无法确定具体类型。

    List<? super Integer> list = new ArrayList<Number>(); // 逆变,允许
    list.add(new Integer(123)); // OK, 可以写入Integer及其子类
    Integer num = list.get(0); // 编译错误!无法安全读取
    Object obj = list.get(0); // OK, 只能读取为Object

5. PECS 原则总结

  • Producer-Extends (P-E):如果你需要一个提供(生产) T 对象的泛型结构(主要调用 get()),使用 <? extends T>。例如:Collection<? extends T>.get()

  • Consumer-Super (C-S):如果你需要一个接收(消费) T 对象的泛型结构(主要调用 add()),使用 <? super T>。例如:Collection<? super T>.add(T)

  • 既生产又消费:如果你既要读又要写,那就不要用通配符,直接用确切的类型,如 <T>

经典应用:Collections.copy()

public static <T> void copy(List<? super T> dest, List<? extends T> src) {// dest 是消费者 (Consumer),消费T对象,所以用 ? super T// src 是生产者 (Producer),生产T对象,所以用 ? extends Tfor (int i =0; i < src.size(); i++) {dest.set(i, src.get(i));}
}

五、常见问题总结

Q:“详细讲讲Java的泛型。”

A:

“Java泛型的核心目的是提供编译时类型安全消除强制类型转换。它的实现机制是类型擦除,即在编译后泛型信息会被移除,类型参数会被替换为它的边界或Object,并由编译器自动插入强制转换。

类型擦除带来了一些限制,比如不能使用基本类型、不能进行泛型的instanceof检查、不能创建泛型数组等。为了解决擦除与多态的冲突,编译器会生成桥接方法,它在子类重写泛型方法时,负责进行类型检查和安全转型,从而保证多态性。

泛型具有不变性Box<String> 不是 Box<Object> 的子类。为了更灵活的API设计,引入了通配符 ? 和 PECS原则

  • ? extends T 用于生产者,表示可以安全读取,但不能写入。

  • ? super T 用于消费者,表示可以安全写入,但读取受限。

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

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

相关文章

Redis开发06:使用stackexchange.redis库结合WebAPI对redis进行增删改查

一、接口写法namespace WebApplication1.Controllers.Redis {[ApiController][Route("/api/[controller]")]public class RedisService : IRedisService{private readonly IConnectionMultiplexer _redis;//StackExchange.Redis库自带接口public RedisService(IConne…

【前端教程】从零开始学JavaScript交互:7个经典事件处理案例解析

在网页开发中&#xff0c;交互性是提升用户体验的关键。JavaScript作为网页交互的核心语言&#xff0c;通过事件处理机制让静态页面"动"了起来。本文将通过7个经典案例&#xff0c;从简单到复杂&#xff0c;逐步讲解JavaScript事件处理的实现方法和应用场景。 案例1&…

内存模型(Memory Model)是什么?

内存模型(Memory Model)是什么? 内存模型是一个非常深刻且核心的计算机科学概念。 核心摘要 内存模型是一个契约或协议,它精确定义了: 一个线程对共享内存的写操作,如何以及何时对其他线程可见。 内存操作(读/写)可以被重新排序的程度。 它连接了硬件(CPU如何执行指令…

在 MyBatis 中oracle基本数值类型的 JDBC 类型映射

基本数值类型的 JDBC 类型Java 类型JDBC 类型 (jdbcType)说明int / IntegerINTEGER标准整数类型long / LongBIGINT大整数类型short / ShortSMALLINT小整数类型float / FloatFLOAT单精度浮点数double / DoubleDOUBLE双精度浮点数java.math.BigDecimalDECIMAL高精度小数&#xff…

Spring注解演进与自动装配原理深度解析:从历史发展到自定义Starter实践

目录 Spring注解发展史 Spring 1.X Spring 2.X Spring 2.5之前 Required Repository Aspect Spring2.5 之后 Spring 3.x ComponentScan Import 静态导入 ImportSelector ImportBeanDefinitionRegistrar EnableXXX Spring 4.x Spring 5.x 什么是SPI 自动装配…

第三届机械工程与先进制造智能化技术研讨会(MEAMIT2025)

【重要信息】 大会官网&#xff1a;https://www.yanfajia.com/action/p/BYE27DYDhttps://www.yanfajia.com/action/p/BYE27DYD 会议地点&#xff1a;哈尔滨理工大学 论文检索&#xff1a;EI Compendex、Scopus 还有部份版面&#xff0c;欲投稿从速&#xff0c;即将提交出版…

笔记本电脑频繁出现 vcomp140.dll丢失怎么办?结合移动设备特性,提供适配性强的修复方案

对于需要用电脑处理工作的人来说&#xff0c;“vcomp140.dll 丢失” 导致程序频繁闪退&#xff0c;无疑会严重影响工作效率。尝试重启电脑、重新安装软件后&#xff0c;问题依然存在&#xff0c;这时候该怎么办&#xff1f;别着急&#xff0c;vcomp140.dll 丢失看似棘手&#x…

微动开关-电竞鼠标核心!5000万次寿命微动开关评测

一、主流电竞微动开关技术对比‌光磁微动技术‌采用非接触式光学触发原理理论寿命突破5000万次触发响应速度0.2ms‌‌传统机械微动‌欧姆龙D2FC-F-7N系列5000万次标称寿命机械结构简单可靠‌‌创新结构微动‌双飞燕漆蓝荧光微动特殊涂层提升耐久性手感反馈独特‌二、5000万次寿…

Go语言与Docker 开发的核心应用领域

1. 容器化应用构建与部署‌‌轻量化镜像构建Go 语言编译生成静态二进制文件&#xff0c;结合多阶段构建的 Dockerfile&#xff0c;可大幅缩小镜像体积&#xff08;例如使用 scratch 或 alpine 基础镜像&#xff09;&#xff0c;提升部署效率‌。示例 Dockerfile 片段&#xff1…

【ESP32-IDF】网络连接开发2:Wi‑Fi 智能配网(SmartConfig)

系列文章目录 持续更新… 文章目录系列文章目录前言一、Wi‑Fi 智能配网概述1.SmartConfig 简介2.SmartConfig 工作原理3.SmartConfig 协议类型二、Wi‑Fi 智能配网类型定义及相关API三、Wi‑Fi 智能配网示例程序总结前言 在物联网设备开发过程中&#xff0c;如果将 Wi-Fi 的…

CVPR深度学习研究指南:特征提取模块仍是论文创新难点

关注gongzhonghao【CVPR顶会精选】在深度学习赛道里&#xff0c;别只盯着堆模型卷参数了。最近不少高分工作都在打“可解释”这张牌&#xff0c;把原本难以理解的黑箱模型用轻量方法剖开&#xff0c;既能增强学术价值&#xff0c;还能拓展落地场景。更妙的是&#xff0c;这类研…

redis----list详解

列表&#xff08;List&#xff09;相当于数组或者顺序表一、通用命令LPUSH key value1 [value2 ...]在列表 key 的左侧&#xff08;头部&#xff09;插入一个或多个值。示例&#xff1a;LPUSH fruits apple banana → 列表变为 [banana, apple]LPUSHX 只有列表已存在时才会执行…

【python】相机输出图片时保留时间戳数据

有时候需要参考时间戳&#xff0c;写个笔记记录下 但是输出时间可能不稳&#xff0c;有待进一步优化 import cv2 import time import os# 创建一个保存图像的文件夹 output_folder "camera_images" if not os.path.exists(output_folder):os.makedirs(output_folder…

(Nginx)基于Nginx+PHP 驱动 Web 应用(上):配置文件与虚拟主机篇

1.应用场景 主要用于学习基于 Nginx PHP 驱动 Web 应用&#xff08;上&#xff09;&#xff1a; 配置文件与虚拟主机篇&#xff0c;学习弄清楚Nginx的常规操作&#xff0c;之前困惑的地方。 本文主要介绍了基于NginxPHP驱动Web应用的配置方法&#xff0c;重点讲解了Nginx配置…

【golang长途旅行第34站】网络编程

网络编程 基本介绍核心主题&#xff1a;​​ Golang面向大规模后端服务程序的设计目标中&#xff0c;网络通信是必不可少且至关重要的部分。​两种网络编程方式&#xff1a;​​​TCP Socket编程​ •性质&#xff1a;网络编程的主流 •底层协议&#xff1a;基于TCP/IP协议 •举…

Hadoop(六)

目录&#xff1a;1.Hadoop概述2.为什么需要分布式存储3.分布式的基础架构分析4.HDFS的基础架构1.Hadoop概述2.为什么需要分布式存储3.分布式的基础架构分析4.HDFS的基础架构

Oracle 12g安装

1. 下载地址 官方网站 一般这种导向的进入的都是oracle的官方网站(先登录&#xff0c;如果没有就创建账号)&#xff0c;并没有真实的12g供你下载。需要你转入Oracle的云中下载&#xff1a;https://edelivery.oracle.com/osdc/faces/SoftwareDelivery 。我选择的是12.1.0.2.0下…

ros2--service/服务--接口

获取service名称const char *get_service_name() const;std::string client_name client_->get_service_name();RCLCPP_INFO(this->get_logger(), "Client name: %s", client_name.c_str());

安卓开发---SimpleAdapter

概念&#xff1a;SimpleAdapter 是 Android 中比 ArrayAdapter 更强大的适配器&#xff0c;用于将复杂的数据绑定到复杂的布局&#xff0c;支持将 Map 中的数据映射到布局中的多个 View。方法签名&#xff1a;public SimpleAdapter( Context context, //上下文 List<? exte…

软考-系统架构设计师 办公自动化系统(OAS)详细讲解

个人博客&#xff1a;blogs.wurp.top 一、OAS的核心概念与演进 1. 什么是OAS&#xff1f; OAS是一个综合性的信息系统&#xff0c;它利用计算机技术、通信技术、系统科学和行为科学&#xff0c;为组织的日常办公事务、信息管理和协同工作提供支持。其本质是将传统办公流程电…