当前位置: 首页 > news >正文

Java 泛型的“擦除”与“保留”:一次完整的编译与反编译实验

Java 的泛型常常被称为“伪泛型”,因为运行时类型系统中是擦除式泛型。然而,我们又能在框架(如 Jackson 的 TypeReference、Guava 的 TypeToken)中看到它们精准地识别出复杂的泛型结构。这背后到底发生了什么?

本文通过一个小实验,带大家从源码、编译、字节码、反编译、再到反射调用,完整地理解 泛型擦除的时机泛型信息保留的方式


一、准备实验代码

我们先自己写一个简化版的 TypeReference(避免引入 Jackson 依赖),核心思想就是:在匿名子类的父类签名里,编译器会留下泛型信息,我们在构造器里把它读出来

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;/*** 简化版的 TypeReference*/
abstract class TypeReference<T> {protected final Type type;protected TypeReference() {Type superClass = getClass().getGenericSuperclass();this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0];}public Type getType() {return type;}
}public class Demo {public static void main(String[] args) {TypeReference<List<String>> ref = new TypeReference<List<String>>() {};System.out.println("Captured type = " + ref.getType());}
}

运行后输出:

Captured type = java.util.List<java.lang.String>

二、编译生成 class 文件

用 JDK 编译:

javac Demo.java

会生成两个字节码文件:

Demo.class      # 主类
Demo$1.class    # 匿名子类(new TypeReference<...>() {})

三、字节码观察

1. 主类(Demo.class)

反编译:

javap -c Demo.class

关键片段:

0: new           #2   // class per/mjn/webflux_demo/Demo$1
3: dup
4: invokespecial #3   // Method per/mjn/webflux_demo/Demo$1."<init>":()V
7: astore_1

可以看到,这里只是在创建 Demo$1 实例,类型信息完全擦除了,看不到 <List<String>>


2. 匿名类(Demo$1.class)

反编译:

javap -v Demo$1.class

输出片段:

class per.mjn.webflux_demo.Demo$1 extends per.mjn.webflux_demo.TypeReference<java.util.List<java.lang.String>>Signature: #9                           // Lper/mjn/webflux_demo/TypeReference<Ljava/util/List<Ljava/lang/String;>;>;

这里就是关键证据:

  • 类头部:显示了 extends TypeReference<List<String>>
  • Signature 属性LTypeReference<Ljava/util/List<Ljava/lang/String;>;>;
    这是编译器在 class 文件中额外写入的泛型元数据

四、运行时反射的利用

TypeReference 构造器中,我们调用:

Type superClass = getClass().getGenericSuperclass();

这个方法正是读取了 class 文件里的 Signature 元数据,然后把泛型参数还原成 ParameterizedType,所以可以打印出 List<String>


五、整个过程的时序梳理

如下图所示:

Java源代码编译器(javac).class文件JVM运行时反射API编译检查泛型 (List<String>)擦除泛型 ->> List写入字节码(擦除后的类型)同时写入 Signature 元数据 (保留泛型)类加载类型检查基于擦除后的类型提供 Signature 元数据还原出 List<String>Java源代码编译器(javac).class文件JVM运行时反射API

六、总结

  1. 泛型擦除的时机
    编译期,生成字节码前,所有泛型参数都被擦除为原始类型(如 Object)。

  2. 泛型信息保留的时机
    编译器在写 .class 文件时,会在 Signature 属性里附带泛型参数信息。

  3. 运行时 JVM 类型系统
    JVM 并不会在运行时保留泛型参数作为真正的类型信息,所有的类型检查都依赖编译期擦除后的原始类型。

  4. 反射如何利用
    通过 getGenericSuperclass()getGenericInterfaces() 等方法,能读取 class 文件中的 Signature,从而还原完整泛型结构。这就是 TypeReferenceTypeToken 等工具的原理。


七、启发

  • Java 的泛型并不是彻底消失:擦除用于运行时类型检查,Signature 保留用于反射与工具框架
  • 熟悉这套机制,能帮助我们理解:为什么 List<String> 在运行时是 ArrayList,却又能被 Jackson 精确反序列化成 List<Map<String,Integer>>

这个小实验展示了 Java 泛型的“擦除”与“保留”如何共存。理解这一点,不仅能解答“TypeReference 为何能拿到泛型参数”的疑惑,也能帮助我们写出更强大的框架级代码。

http://www.dtcms.com/a/348996.html

相关文章:

  • Docker中Dify镜像由Windows系统迁移到Linux系统的方法
  • 【计算机408数据结构】第二章:基本数据结构之线性表
  • Leetcode 3660. Jump Game IX
  • 新的 Gmail 网络钓鱼攻击利用 AI 提示注入来逃避检测
  • rust语言 (1.88) egui (0.32.1) 学习笔记(逐行注释)(十四)垂直滚动条
  • 【URP】[投影Projector]解析与应用
  • 【cs336学习笔记】[第6课]内核优化与Triton框架应用
  • 如何在算力时代乘风破浪?
  • 深度学习中的模型量化及实现示例
  • 【RAGFlow代码详解-4】数据存储层
  • MySQL学习记录-基础知识及SQL语句
  • 【零代码】OpenCV C# 快速开发框架演示
  • 在 Docker 容器中查看 Python 版本
  • C语言第十二章自定义类型:结构体
  • LangChain RAG系统开发基础学习之文档切分
  • Python核心技术开发指南(016)——表达式
  • 多线程——认识Thread类和创建线程
  • 【记录】Docker|Docker镜像拉取超时的问题、推荐的解决办法及安全校验
  • FPGA时序分析(四)
  • asio的线程安全
  • 使用Cobra 完成CLI开发 (一)
  • 3.1 存储系统概述 (答案见原书 P149)
  • C++ string自定义类的实现
  • 【论文阅读 | arXiv 2025 | WaveMamba:面向RGB-红外目标检测的小波驱动Mamba融合方法】
  • 上科大解锁城市建模新视角!AerialGo:从航拍视角到地面漫步的3D城市重建
  • 深度剖析Spring AI源码(三):ChatClient详解,优雅的流式API设计
  • R60ABD1 串口通信实现
  • 在 Ubuntu 24.04 或 22.04 LTS 服务器上安装、配置和使用 Fail2ban
  • 【Qwen Image】蒸馏版与非蒸馏版 评测小结
  • 第3篇:配置管理的艺术 - 让框架更灵活