news 2026/7/23 19:23:23

什么是java泛型(Type Erasure)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
什么是java泛型(Type Erasure)

如果不用泛型,而使用Object类型,那么每次执行完之后,我们即使得到这个结果,还得类型转换一下,那么就很容易出现类型转换异常,泛型的出现避免了进行类型转换。

泛型的使用

例如public class GenericClass<T> {}中的T便是泛型,java是强类型语言,使用泛型时需要显式声明泛型T的类型,例如创建对象new GenericClass<String>,或者例如泛型方法:

public <T> void genericMethod(T a){

Log.d("harvic","genericMethod: "+a.toString());

}

泛型方法中<T>用于声明独立的参数类型,表示方法参数中的T是独立的,而不是GenericClass<T>中的T。

class GenericClass<T> { // 类级泛型 T

public <T> void genericMethod(T param) { // 方法级泛型 T(遮蔽类级T)

// 此处的 T 与类级 T 无关!

}

}

在使用时应该这样调用genericClass.<Integer>genericMethod(new Integer(123));

其实中间的<Integer>也可以不写,即隐式传递让编译器自己推断T的类型,但是显示声明是一种更标准的写法。

参考:

Java泛型详解:<T>和Class<T>的使用。泛型类,泛型方法的详细使用实例 - 星朝 - 博客园

泛型之类型擦除:

Java 的泛型是编译期特性,例如List<Integer>编译前会检查添加的元素是否为提供的泛型类型Integer,在运行前会进行类型擦除(Type Erasure),无论是List<Integer>还是List<String>,其泛型的类型都会被擦除成为Object,即运行时集合的类型为List<Object>,我们可以通过反射来向List<Integer>中添加String元素来绕开编译检查。

ArrayList<Integer>list=newArrayList<Integer>();

list.add(1);//这样调用add方法只能存储整形,因为泛型类型的实例为Integer
Integer a=list.get(0);

// String a = list.get(0); 会报错
list.getClass().getMethod("add",Object.class).invoke(list,"asd");

Object obj=list.get(0);//允许,因为所有类都是Object的子类
String s= (String)obj;

既然最终类型擦除为了Object,为何取出元素时可以用Integer接收而不能用String接收呢?

实际上在编译后编译器会在list.get(0)左侧插入(Integer)来强转,如果用Object a来接收则直接接收不会隐式强转,另外,list.get(0)这个元素的对象的类型仍然是Integer类型的,用Object a接收时只是声明了一个Object类型的引用变量指向list.get(0)这个Integer对象,实际上所谓的擦除(强转)并没有真的将Integer对象转成了Object,而是将对象的Integer类型的引用转成了Object类型的引用。而String类型的引用无法指向Integer对象,是因为引用变量的类型必须是右侧对象类型的父类,有这样的规则:父类引用指向子类对象(向上转型,自动完成),子类引用不能指向父类对象(不能向下转型)或者引用类型和对象类型之间没有继承关系也不能指向,但是父类引用指向子类对象时,此父类引用可强转为子类引用

参考:

Java泛型中的类型擦除详解 - 知乎

为何要进行类型擦除?

简而言之,为了让旧版本的java项目代码可以在不修改项目代码的情况下无缝升级到新版本java。

例如使用老版本java(不支持泛型)的项目代码是这样的:

Node node = new Node();

泛型版本是这样的:

Node<String> node = new Node<String>();

如图:

这样老版本代码Node node = new Node();即使不加泛型信息也会和Node node = new Node<String>();一样,最终被替换为Node node = new Node();,这样就可在不改老版本java代码的情况下利用编译器实现伪泛型。

参考:

为什么Java的泛型要用"擦除"实现 - 简书

参考中举例的ArrayList并不合适,ArrayList毕竟是jdk自带的包,不会存在左边声明的ArrayList和右边new的ArrayList不是同一个类的情况,而且即使(自定义类加载器实现加载)同包同名的两个类也是不同的类,是不能互相引用赋值的。

所谓泛型擦除实际就是为了兼容老版本的写法,那为何不将老版本的无泛型写法和新版本的泛型写法都编译为真泛型类,实现真正的泛型呢?

因为泛型擦除后,JVM处理的就是原生类,无需改动JVM源码,而如果都编译为真泛型类,那么就需要对JVM额外引入泛型类,要改动JVM源码,这改动较大,不如在编译期将泛型擦除为原生类来的更快。

为何匿名内部类可获取泛型类型信息?

匿名内部类,实际上是一个继承了父类的子类或是一个实现了接口的实现类,会单独生成一个类文件,父类/接口 和匿名内部类的泛型类型变量实际上还是被擦除成了Object,但是匿名内部类会在自己类文件的签名中保留父类泛型的实际类型参数信息(例如List<String>中String就是实际类型参数)

注意是匿名内部类的.class类文件中保留了父类泛型的实际类型参数信息,而匿名内部类的对象中的泛型信息依然是被擦除了。而泛型类本身的类文件自然是不会有实际类型参数信息的,因为泛型类被那么多的地方引用,如果保留实际类型参数信息便会冲突失去了其“泛”的功能。

例如:

Flink 使用 匿名内部类(Anonymous Inner Class) 来保留父类泛型的实际类型参数信息,并通过反射机制提取它。

匿名子类会在其类文件的元数据中保留父类泛型的实际类型参数信息(如:OutputTag<JsonNode>)

参考:

Flink OutputTag 为什么需要加 "{}" - chouc - 博客园

扩展

匿名内部类靠继承指定了类型参数的父类来保留实际类型参数信息,那么普通子类继承一个具体类型的泛型类也能保留实际类型参数信息,此类型信息也保留在子类的类文件签名中,可通过反射获取。

应当说匿名内部类保留实际类型参数信息也是依靠“子类保留父类实际类型参数信息”这一原理,只不过匿名内部类是一种更简单的继承写法。

什么是匿名内部类?

参考:JAVA匿名内部类(Anonymous Classes) - 无恨之都 - 博客园

泛型 T 和 ? 的区别

List<T>,这个T是一个形参,可以理解为一个占位符,被使用时,会在程序运行的时候替换成具体的类型,比如替换成String,Integer之类的。

List<?>,这个“?”是一个实参,这是Java定义的一种特殊类型,比Object更特殊,是一个用于匹配所有类型的通配符,而不是一个占位符。比如List<Object>和List<String>是没有父子关系的,这是两个类型——List<Object>类型和List<String>类型;但是List<?> 是List<String>的父类。

用extends限定类型子集的时候,?不能多重继承,T 可以;

使用super限定父集的时候,? 可以, T 不可以。

我的理解

List<?>可以匹配List<String>、List<Integer>、List<Long>......,而List<T>中的T是一个类型变量,假设给一个方法传入一个List<String>,方法参数声明为List<T>代表需要手动传入一个类型T,这个T此时为String,而方法参数声明为List<?>代表直接匹配了传入的List<String>,就好比直接在方法参数上声明了一个List<String>来匹配,因此说T为形参,而?为实参。

也因此,二者使用地方不同,T可作为声明类的类型参数、方法参数,而?只能作为声明变量(方法参数变量、成员变量的类型字段或局部变量的类型字段,即Object<?> a)的类型参数。

参考:

Java泛型通配符 ? 与 T 的区别 - 沙湖王 - SegmentFault 思否

Java 泛型 T,E,K,V,?,傻傻分不清?-腾讯云开发者社区-腾讯云

泛型上下界的含义

泛型上限:

<? extends T> 表示泛型的上界通配符,适用于频繁获取数据的场景。它限制了泛型类型只能是指定类型或其子类。例如:

List<? extends Fruit> fruits = new ArrayList<Apple>();

在这个例子中,fruits可以是Fruit或Fruit任何子类的列表,如List<Apple>。

这里,“?”是一个具体的未知类型,且是Fruit的子类,向列表中添加元素相当于将其中的某一个“?”类型的元素引用赋值为Apple对象,又根据前面说的“父类引用指向子类对象,子类引用不能指向父类对象”,而此“?”类型不是Apple类型的父类,而是与Apple同属于Fruit的子类,所以不能向这个列表中添加任何元素(要添加也只能添加null),只能从中获取元素。

总结:List<? extends Fruit> fruits可赋值为List<Fruit>及其子类列表,但不能向其中添加任何元素。

泛型下限:

<? super T> 表示泛型的下界通配符,适用于频繁添加数据的场景。它限制了泛型类型只能是指定类型或其父类。例如:

List<? super Apple> apples = new ArrayList<Fruit>();

在这个例子中,apples可以是Apple或Apple任何父类的列表,如List<Fruit>。

这里,“?”是一个具体的未知类型,且是Apple的父类,向列表中添加元素相当于将其中的某一个“?”类型的元素引用赋值为Fruit对象,又根据前面说的“父类引用指向子类对象,子类引用不能指向父类对象”,而此“?”类型是Apple类型的父类,你可以向这个列表中添加Apple或Apple子类的对象,但因为“?”是未知类型,从中获取的元素只能用Object类型接收(可强转为原本类型)。

总结:List<? super Apple> apples可赋值为List<Apple>及其父类列表,可向其中添加Apple或Apple子类元素,取元素要用Object类型接收。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/23 19:21:23

原神6.6剧情GM夏活后台单机版免费分享

下载&#xff1a;全部下载下来 解压&#xff1a;右键解压至任意文件夹&#xff08;建议关闭杀毒软件&#xff09;打补丁&#xff1a;下载的是6.5的包体&#xff0c;压缩包文件夹里面有额外的6.6的补丁&#xff0c;拖入游戏根目录即可&#xff08;有视频教程&#xff09;&#x…

作者头像 李华
网站建设 2026/7/23 19:20:48

软路由雷达:基于OpenWrt系统的传统逆向思路的冷门实现

近期5E对战平台“顺藤摸瓜”收网行动的结果公告中&#xff0c;“软路由雷达”作为一种新型作弊方式进入到大众视野。这篇文章便是科普软路由雷达本质及其背后的实现逻辑&#xff0c;同时探讨游戏反作弊应如何针对此类“新型作弊”做出检测。 一、核心原理解析 中间人攻击&#…

作者头像 李华
网站建设 2026/7/23 19:19:28

verilog HDLBits刷题[Counters]“Exams/ece241 ”---Counter 1-12

一、题目Design a 1-12 counter with the following inputs and outputs:Reset Synchronous active-high reset that forces the counter to 1Enable Set high for the counter to runClk Positive edge-triggered clock inputQ[3:0] The output of the counterc_enable, c_loa…

作者头像 李华
网站建设 2026/7/23 19:18:13

AI发展中的算力、模型与数据三角关系解析

1. 项目概述&#xff1a;AI时代的核心矛盾与突破路径2016年AlphaGo战胜李世石时&#xff0c;多数人只看到了AI的惊艳表现&#xff0c;却忽略了背后每天消耗的3000美元电费。这个细节揭示了AI发展的底层逻辑&#xff1a;算力、模型和数据构成的铁三角&#xff0c;正在成为决定AI…

作者头像 李华