Java泛型(5)泛型擦除
1.官方文档
Type Erasure | https://docs.oracle.com/javase/tutorial/java/generics/erasure.html |
Erasure of Generic Types | https://docs.oracle.com/javase/tutorial/java/generics/genTypes.html |
Erasure of Generic Methods | https://docs.oracle.com/javase/tutorial/java/generics/genMethods.html |
Effects of Type Erasure and Bridge Methods | https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html |
Non-Reifiable Types | https://docs.oracle.com/javase/tutorial/java/generics/nonReifiableVarargsType.html |
2. 泛型擦除
2.1 简介
Generics were introduced to the Java language to provide tighter type checks at compile time and to support generic programming. To implement generics, the Java compiler applies type erasure to:
- Replace all type parameters in generic types with their first bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
- Insert type casts if necessary to preserve type safety.
- Generate bridge methods to preserve polymorphism in extended generic types.
Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.
- java 编译器会把泛型类、泛型函数中的泛型擦除掉,用泛型声明时第1个边界类型代替代码中的泛型,然后生成相应的类,若未指定边界类时,默认用Object替换。在对应的字节码中没有泛型信息。
- 在生成的类中,如果有类型相关操作,则自动插入相应的类型转换代码。
- 当某类继承具体的泛型类、泛型接口时,如 class MyNode extends Node<Integer> 时,编译器执行以下操作:
- 先对泛型擦除,用相应类型替换泛型。
- 给子类插入一个函数来保证对父类函数的覆盖。
- 在插入的函数内部调用子类手动实现的函数,如需要则插入强转代码。详见2.5 synthetic Bridge
2.2 简单示例
public class Vector<T> { public T[] data; public void push_back(T t) { } public boolean contains(Vector<T> other){return false;} }
其中的 <T>只擦除无替换,T 被擦除然后用Object替换,生成如下的java类:
public class Vector { public Object[] data; public void push_back(Object t) { } public boolean contains(Vector other){return false;} }
对应的字节码如下:
// class version 55.0 (55) // access flags 0x21 // signature <T:Ljava/lang/Object;>Ljava/lang/Object; // declaration: com/example/sjjg/utest/app/Vector<T> public class com/example/sjjg/utest/app/Vector { // compiled from: Vector.java // access flags 0x1 // signature [TT; // declaration: data extends T[] public [Ljava/lang/Object; data // access flags 0x1 public <init>()V L0 LINENUMBER 3 L0 ALOAD 0 INVOKESPECIAL java/lang/Object.<init> ()V RETURN L1 LOCALVARIABLE this Lcom/example/sjjg/utest/app/Vector; L0 L1 0 // signature Lcom/example/sjjg/utest/app/Vector<TT;>; // declaration: this extends com.example.sjjg.utest.app.Vector<T> MAXSTACK = 1 MAXLOCALS = 1 // access flags 0x1 // signature (TT;)V // declaration: void push_back(T) public push_back(Ljava/lang/Object;)V L0 LINENUMBER 5 L0 RETURN L1 LOCALVARIABLE this Lcom/example/sjjg/utest/app/Vector; L0 L1 0 // signature Lcom/example/sjjg/utest/app/Vector<TT;>; // declaration: this extends com.example.sjjg.utest.app.Vector<T> LOCALVARIABLE t Ljava/lang/Object; L0 L1 1 // signature TT; // declaration: t extends T MAXSTACK = 0 MAXLOCALS = 2 // access flags 0x1 // signature (Lcom/example/sjjg/utest/app/Vector<TT;>;)Z // declaration: boolean contains(com.example.sjjg.utest.app.Vector<T>) public contains(Lcom/example/sjjg/utest/app/Vector;)Z L0 LINENUMBER 6 L0 ICONST_0 IRETURN L1 LOCALVARIABLE this Lcom/example/sjjg/utest/app/Vector; L0 L1 0 // signature Lcom/example/sjjg/utest/app/Vector<TT;>; // declaration: this extends com.example.sjjg.utest.app.Vector<T> LOCALVARIABLE other Lcom/example/sjjg/utest/app/Vector; L0 L1 1 // signature Lcom/example/sjjg/utest/app/Vector<TT;>; // declaration: other extends com.example.sjjg.utest.app.Vector<T> MAXSTACK = 1 MAXLOCALS = 2 }
3.边界类型替换
public class MyNumber<T extends Number & Serializable & Runnable,List> { public T value; }
T 继承了Number,实现了多个接口,这时用最左边的Number替换。注意其中的List无效。生成的类如下:
public class MyNumber { public Number value; }
把上面的extends 顺序改一下试试:
public class MyNumber<T extends Serializable & Runnable,List> { public T value; }
注意其中的List无效,生成的类如下:
public class MyNumber { public Serializable value; }
再添加一个自定义的接口
interface INumber1{} public class MyNumber<T extends INumber1 & Runnable & Serializable,Object,第1个逗号后面没有意义,这里可以写成这样没,ldkeo292jd> { public T value; }
生成的类如下:
public class MyNumber { public INumber1 value; }
4.擦除函数中的泛型
public class MyNumber <T extends Comparable<T>,Object,第1个逗号后面没有意义,这里可以写成这样,ldkeo292jd> { public T value; public void add(T x ) {} public void add(T []arr ) {} public <U extends Number> void add(U u) {} public void add(Collection<? super Integer > ns){} }
生成的类如下:
public class MyNumber { public Comparable value; public void add(Comparable x ){} public void add(Comparable []arr ){} public void add(Number u ){} public void add(Collection ns ){} }
其中的add函数的字节码如下:
// access flags 0x1 // signature (TT;)V // declaration: void add(T) public add(Ljava/lang/Comparable;)V L0 LINENUMBER 8 L0 RETURN L1 LOCALVARIABLE this Lcom/example/sjjg/utest/app/MyNumber; L0 L1 0 // signature Lcom/example/sjjg/utest/app/MyNumber<TT;TObject;T第1个逗号后面没有意义;T这里可以写成这样;Tldkeo292jd;>; // declaration: this extends com.example.sjjg.utest.app.MyNumber<T, Object, 第1个逗号后面没有意义, 这里可以写成这样, ldkeo292jd> LOCALVARIABLE x Ljava/lang/Comparable; L0 L1 1 // signature TT; // declaration: x extends T MAXSTACK = 0 MAXLOCALS = 2 // access flags 0x1 // signature ([TT;)V // declaration: void add(T[]) public add([Ljava/lang/Comparable;)V L0 LINENUMBER 9 L0 RETURN L1 LOCALVARIABLE this Lcom/example/sjjg/utest/app/MyNumber; L0 L1 0 // signature Lcom/example/sjjg/utest/app/MyNumber<TT;TObject;T第1个逗号后面没有意义;T这里可以写成这样;Tldkeo292jd;>; // declaration: this extends com.example.sjjg.utest.app.MyNumber<T, Object, 第1个逗号后面没有意义, 这里可以写成这样, ldkeo292jd> LOCALVARIABLE arr [Ljava/lang/Comparable; L0 L1 1 // signature [TT; // declaration: arr extends T[] MAXSTACK = 0 MAXLOCALS = 2 // access flags 0x1 // signature <U:Ljava/lang/Number;>(TU;)V // declaration: void add<U extends java.lang.Number>(U) public add(Ljava/lang/Number;)V L0 LINENUMBER 10 L0 RETURN L1 LOCALVARIABLE this Lcom/example/sjjg/utest/app/MyNumber; L0 L1 0 // signature Lcom/example/sjjg/utest/app/MyNumber<TT;TObject;T第1个逗号后面没有意义;T这里可以写成这样;Tldkeo292jd;>; // declaration: this extends com.example.sjjg.utest.app.MyNumber<T, Object, 第1个逗号后面没有意义, 这里可以写成这样, ldkeo292jd> LOCALVARIABLE u Ljava/lang/Number; L0 L1 1 // signature TU; // declaration: u extends U MAXSTACK = 0 MAXLOCALS = 2 // access flags 0x1 // signature (Ljava/util/Collection<-Ljava/lang/Integer;>;)V // declaration: void add(java.util.Collection<? super java.lang.Integer>) public add(Ljava/util/Collection;)V L0 LINENUMBER 11 L0 RETURN L1 LOCALVARIABLE this Lcom/example/sjjg/utest/app/MyNumber; L0 L1 0 // signature Lcom/example/sjjg/utest/app/MyNumber<TT;TObject;T第1个逗号后面没有意义;T这里可以写成这样;Tldkeo292jd;>; // declaration: this extends com.example.sjjg.utest.app.MyNumber<T, Object, 第1个逗号后面没有意义, 这里可以写成这样, ldkeo292jd> LOCALVARIABLE ns Ljava/util/Collection; L0 L1 1 // signature Ljava/util/Collection<-Ljava/lang/Integer;>; // declaration: ns extends java.util.Collection<? super java.lang.Integer> MAXSTACK = 0 MAXLOCALS = 2
5.桥接函数
5.1 作用
当某个类继承泛型类的某个具体类型时,覆盖的是具体类型的方法,如foo(Integer),而父类经泛型擦除后生成的是foo(Object),这时编译器的子类中生成一个桥函数foo(Object)完成覆盖,并且在这个函数中调用foo(Integer)。
5.2 示例
父类
public class Vector<T> { public T[] data; public void push_back(T t) { } public boolean contains(Vector<T> other){return false;} }
子类继承父类的<Integer>,并且覆盖了void push_back(T t) 这个函数。
public class VectorInt extends Vector<Integer>{ @Override public void push_back(Integer integer) { super.push_back(integer); } }
经泛型擦除后,生成的类如下:其中 的push_back(Object obj)就是桥接函数
public class VectorInt extends Vector{ @Override public void push_back(Object obj) { push_back((Integer) obj); } public void push_back(Integer integer) { } }
VectorInt对应的字节码如下:
public class com/example/sjjg/utest/app/VectorInt extends com/example/sjjg/utest/app/Vector { // compiled from: VectorInt.java // access flags 0x1 public <init>()V L0 LINENUMBER 3 L0 ALOAD 0 INVOKESPECIAL com/example/sjjg/utest/app/Vector.<init> ()V RETURN L1 LOCALVARIABLE this Lcom/example/sjjg/utest/app/VectorInt; L0 L1 0 MAXSTACK = 1 MAXLOCALS = 1 // access flags 0x1 public push_back(Ljava/lang/Integer;)V L0 LINENUMBER 6 L0 ALOAD 0 ALOAD 1 INVOKESPECIAL com/example/sjjg/utest/app/Vector.push_back (Ljava/lang/Object;)V L1 LINENUMBER 7 L1 RETURN L2 LOCALVARIABLE this Lcom/example/sjjg/utest/app/VectorInt; L0 L2 0 LOCALVARIABLE integer Ljava/lang/Integer; L0 L2 1 MAXSTACK = 2 MAXLOCALS = 2 // access flags 0x1041 public synthetic bridge push_back(Ljava/lang/Object;)V L0 LINENUMBER 3 L0 ALOAD 0 ALOAD 1 CHECKCAST java/lang/Integer INVOKEVIRTUAL com/example/sjjg/utest/app/VectorInt.push_back (Ljava/lang/Integer;)V RETURN L1 LOCALVARIABLE this Lcom/example/sjjg/utest/app/VectorInt; L0 L1 0 MAXSTACK = 2 MAXLOCALS = 2 }
其中
- 第18行的push_back是手动覆盖的
- 第34行的函数是一个synthetic bridge 函数
- 第39行强转了obj
- 第40行调用了手动覆盖的push_back
6.泛型为什么被擦除呢?
考虑与1.5的兼容,1.5之前没有泛型。用的Object实现的其它语言中的泛型方案。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?