什么是java泛型(Type Erasure)

发布时间:2026/7/23 19:24:17
什么是java泛型(Type Erasure) 如果不用泛型而使用Object类型那么每次执行完之后我们即使得到这个结果还得类型转换一下那么就很容易出现类型转换异常泛型的出现避免了进行类型转换。泛型的使用例如public class GenericClassT {}中的T便是泛型java是强类型语言使用泛型时需要显式声明泛型T的类型例如创建对象new GenericClassString或者例如泛型方法public T void genericMethod(T a){Log.d(harvic,genericMethod: a.toString());}泛型方法中T用于声明独立的参数类型表示方法参数中的T是独立的而不是GenericClassT中的T。class GenericClassT { // 类级泛型 Tpublic T void genericMethod(T param) { // 方法级泛型 T遮蔽类级T// 此处的 T 与类级 T 无关}}在使用时应该这样调用genericClass.IntegergenericMethod(new Integer(123));其实中间的Integer也可以不写即隐式传递让编译器自己推断T的类型但是显示声明是一种更标准的写法。参考Java泛型详解T和ClassT的使用。泛型类泛型方法的详细使用实例 - 星朝 - 博客园泛型之类型擦除Java 的泛型是编译期特性例如ListInteger编译前会检查添加的元素是否为提供的泛型类型Integer在运行前会进行类型擦除Type Erasure无论是ListInteger还是ListString其泛型的类型都会被擦除成为Object即运行时集合的类型为ListObject我们可以通过反射来向ListInteger中添加String元素来绕开编译检查。ArrayListIntegerlistnewArrayListInteger();list.add(1);//这样调用add方法只能存储整形因为泛型类型的实例为IntegerInteger alist.get(0);// String a list.get(0); 会报错list.getClass().getMethod(add,Object.class).invoke(list,asd);Object objlist.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();泛型版本是这样的NodeString node new NodeString();如图这样老版本代码Node node new Node();即使不加泛型信息也会和Node node new NodeString();一样最终被替换为Node node new Node();这样就可在不改老版本java代码的情况下利用编译器实现伪泛型。参考为什么Java的泛型要用擦除实现 - 简书参考中举例的ArrayList并不合适ArrayList毕竟是jdk自带的包不会存在左边声明的ArrayList和右边new的ArrayList不是同一个类的情况而且即使自定义类加载器实现加载同包同名的两个类也是不同的类是不能互相引用赋值的。所谓泛型擦除实际就是为了兼容老版本的写法那为何不将老版本的无泛型写法和新版本的泛型写法都编译为真泛型类实现真正的泛型呢因为泛型擦除后JVM处理的就是原生类无需改动JVM源码而如果都编译为真泛型类那么就需要对JVM额外引入泛型类要改动JVM源码这改动较大不如在编译期将泛型擦除为原生类来的更快。为何匿名内部类可获取泛型类型信息匿名内部类实际上是一个继承了父类的子类或是一个实现了接口的实现类会单独生成一个类文件父类/接口 和匿名内部类的泛型类型变量实际上还是被擦除成了Object但是匿名内部类会在自己类文件的签名中保留父类泛型的实际类型参数信息例如ListString中String就是实际类型参数。注意是匿名内部类的.class类文件中保留了父类泛型的实际类型参数信息而匿名内部类的对象中的泛型信息依然是被擦除了。而泛型类本身的类文件自然是不会有实际类型参数信息的因为泛型类被那么多的地方引用如果保留实际类型参数信息便会冲突失去了其“泛”的功能。例如Flink 使用 匿名内部类Anonymous Inner Class 来保留父类泛型的实际类型参数信息并通过反射机制提取它。匿名子类会在其类文件的元数据中保留父类泛型的实际类型参数信息如OutputTagJsonNode参考Flink OutputTag 为什么需要加 {} - chouc - 博客园扩展匿名内部类靠继承指定了类型参数的父类来保留实际类型参数信息那么普通子类继承一个具体类型的泛型类也能保留实际类型参数信息此类型信息也保留在子类的类文件签名中可通过反射获取。应当说匿名内部类保留实际类型参数信息也是依靠“子类保留父类实际类型参数信息”这一原理只不过匿名内部类是一种更简单的继承写法。什么是匿名内部类参考JAVA匿名内部类(Anonymous Classes) - 无恨之都 - 博客园泛型 T 和 ? 的区别ListT这个“T”是一个形参可以理解为一个占位符被使用时会在程序运行的时候替换成具体的类型比如替换成StringInteger之类的。List?这个“?”是一个实参这是Java定义的一种特殊类型比Object更特殊是一个用于匹配所有类型的通配符而不是一个占位符。比如ListObject和ListString是没有父子关系的这是两个类型——ListObject类型和ListString类型但是List? 是ListString的父类。用extends限定类型子集的时候不能多重继承T 可以使用super限定父集的时候 可以 T 不可以。我的理解List?可以匹配ListString、ListInteger、ListLong......而ListT中的T是一个类型变量假设给一个方法传入一个ListString方法参数声明为ListT代表需要手动传入一个类型T这个T此时为String而方法参数声明为List?代表直接匹配了传入的ListString就好比直接在方法参数上声明了一个ListString来匹配因此说T为形参而?为实参。也因此二者使用地方不同T可作为声明类的类型参数、方法参数而?只能作为声明变量方法参数变量、成员变量的类型字段或局部变量的类型字段即Object? a的类型参数。参考Java泛型通配符 ? 与 T 的区别 - 沙湖王 - SegmentFault 思否Java 泛型 TEKV?傻傻分不清-腾讯云开发者社区-腾讯云泛型上下界的含义泛型上限? extends T 表示泛型的上界通配符适用于频繁获取数据的场景。它限制了泛型类型只能是指定类型或其子类。例如List? extends Fruit fruits new ArrayListApple();在这个例子中fruits可以是Fruit或Fruit任何子类的列表如ListApple。这里“?”是一个具体的未知类型且是Fruit的子类向列表中添加元素相当于将其中的某一个“?”类型的元素引用赋值为Apple对象又根据前面说的“父类引用指向子类对象子类引用不能指向父类对象”而此“?”类型不是Apple类型的父类而是与Apple同属于Fruit的子类所以不能向这个列表中添加任何元素要添加也只能添加null只能从中获取元素。总结List? extends Fruit fruits可赋值为ListFruit及其子类列表但不能向其中添加任何元素。泛型下限? super T 表示泛型的下界通配符适用于频繁添加数据的场景。它限制了泛型类型只能是指定类型或其父类。例如List? super Apple apples new ArrayListFruit();在这个例子中apples可以是Apple或Apple任何父类的列表如ListFruit。这里“?”是一个具体的未知类型且是Apple的父类向列表中添加元素相当于将其中的某一个“?”类型的元素引用赋值为Fruit对象又根据前面说的“父类引用指向子类对象子类引用不能指向父类对象”而此“?”类型是Apple类型的父类你可以向这个列表中添加Apple或Apple子类的对象但因为“?”是未知类型从中获取的元素只能用Object类型接收可强转为原本类型。总结List? super Apple apples可赋值为ListApple及其父类列表可向其中添加Apple或Apple子类元素取元素要用Object类型接收。