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> 时,编译器执行以下操作:
    1. 先对泛型擦除,用相应类型替换泛型。
    2. 给子类插入一个函数来保证对父类函数的覆盖。
    3. 在插入的函数内部调用子类手动实现的函数,如需要则插入强转代码。详见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实现的其它语言中的泛型方案。

 

 

 

posted @   f9q  阅读(246)  评论(0编辑  收藏  举报
编辑推荐:
· ASP.NET Core - 日志记录系统(二)
· .NET 依赖注入中的 Captive Dependency
· .NET Core 对象分配(Alloc)底层原理浅谈
· 聊一聊 C#异步 任务延续的三种底层玩法
· 敏捷开发:如何高效开每日站会
阅读排行:
· 终于决定:把自己家的能源管理系统开源了!
· 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(一):从.NET IoT入
· C#实现 Winform 程序在系统托盘显示图标 & 开机自启动
· ASP.NET Core - 日志记录系统(二)
· 实现windows下简单的自动化窗口管理
点击右上角即可分享
微信分享提示