Java泛型之类型擦除

类型擦除

学过C++模板的,在使用Java泛型的时候,会感觉到有点不疑问,例如:(1)无法定义一个泛型数组、无法调用泛型参数对象中对应的方法(当然,通过extends关键字是可以做到,只是比较麻烦);(2)ArrayList和ArrayList在运行时的类型是相同的。Java中的泛型有这些问题,是它的实现机制决定的,即“类型擦除”。

  1. 类型擦除的定义:编译通过后,准备进入JVM运行时,就不再有类型参数的概念,换句话说:每定义一个泛型类型,JVM会自动提供一个对应的原生类;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    public class Holder4<T> {

    private T a;
    private T b;
    private T c;

    public Holder4(T a, T b, T c) {
    this.a = a;
    this.b = b;
    this.c = c;
    }

    public T getA() {
    return a;
    }

    public T getB() {
    return b;
    }

    public T getC() {
    return c;
    }

    public void setA(T a) {
    this.a = a;
    }

    public void setB(T b) {
    this.b = b;
    }

    public void setC(T c) {
    this.c = c;
    }


    public static void main(String[] args) {
    Holder4<Automobile> holder4 = new Holder4<>(new Automobile(),new Automobile(), new Automobile());

    Automobile a = holder4.getA(); //编译器帮忙转型,不需要显式转型
    Automobile b = holder4.getB();
    Automobile c = holder4.getC();
    }
    }

    在Java中,每定义一个泛型类型,就会自动提供一个对应的原始类型,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    public class Holder4Raw {

    private Object a;
    private Object b;
    private Object c;

    public Holder4Raw(Object a, Object b, Object c) {
    this.a = a;
    this.b = b;
    this.c = c;
    }

    public Object getA() {
    return a;
    }

    public Object getB() {
    return b;
    }

    public Object getC() {
    return c;
    }

    public void setA(Object a) {
    this.a = a;
    }

    public void setB(Object b) {
    this.b = b;
    }

    public void setC(Object c) {
    this.c = c;
    }

    public static void main(String[] args) {
    Holder4Raw holder4Raw = new Holder4Raw(new Automobile(),new Automobile(), new Automobile());

    Automobile a = (Automobile) holder4Raw.getA(); //显示的转型
    Automobile b = (Automobile) holder4Raw.getB();
    Automobile c = (Automobile) holder4Raw.getC();
    }
    }
  2. 为什么选择这种实现机制?

    • 在Java诞生10年后,才想实现类似于C++模板的概念,即泛型;
    • Java的类库是Java生态中非常宝贵的财富,必须保证向后兼容(即现有的代码和类文件依旧合法)和迁移兼容(泛化的代码和非泛化的代码可互相调用)基于上面这两个背景和考虑,Java设计者采取了“类型擦除”这种折中的实现方式。
  3. Java泛型依赖编译器实现,只存在于编译期,JVM中没有泛型的概念;那么,编译器做了什么工作呢?(1)set方法是编译期检查;(2)get方法的返回值进行转型,编译器插入了一个checkcast语句。

    我们通过字节码进行观察,可以看出:(1)Holder4和Holder4Raw两个类的字节码完全相同;(2)在main函数的33、41和49行就是编译器插入的checkcast语句;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    public class org.java.learn.generics.Holder4<T> {
    public org.java.learn.generics.Holder4(T, T, T);
    Code:
    0: aload_0
    1: invokespecial #1 // Method java/lang/Object."<init>":()V
    4: aload_0
    5: aload_1
    6: putfield #2 // Field a:Ljava/lang/Object;
    9: aload_0
    10: aload_2
    11: putfield #3 // Field b:Ljava/lang/Object;
    14: aload_0
    15: aload_3
    16: putfield #4 // Field c:Ljava/lang/Object;
    19: return

    public T getA();
    Code:
    0: aload_0
    1: getfield #2 // Field a:Ljava/lang/Object;
    4: areturn

    public T getB();
    Code:
    0: aload_0
    1: getfield #3 // Field b:Ljava/lang/Object;
    4: areturn

    public T getC();
    Code:
    0: aload_0
    1: getfield #4 // Field c:Ljava/lang/Object;
    4: areturn

    public void setA(T);
    Code:
    0: aload_0
    1: aload_1
    2: putfield #2 // Field a:Ljava/lang/Object;
    5: return

    public void setB(T);
    Code:
    0: aload_0
    1: aload_1
    2: putfield #3 // Field b:Ljava/lang/Object;
    5: return

    public void setC(T);
    Code:
    0: aload_0
    1: aload_1
    2: putfield #4 // Field c:Ljava/lang/Object;
    5: return

    public static void main(java.lang.String[]);
    Code:
    0: new #5 // class org/java/learn/generics/Holder4
    3: dup
    4: new #6 // class org/java/learn/generics/Automobile
    7: dup
    8: invokespecial #7 // Method org/java/learn/generics/Automobile."<init>":()V
    11: new #6 // class org/java/learn/generics/Automobile
    14: dup
    15: invokespecial #7 // Method org/java/learn/generics/Automobile."<init>":()V
    18: new #6 // class org/java/learn/generics/Automobile
    21: dup
    22: invokespecial #7 // Method org/java/learn/generics/Automobile."<init>":()V
    25: invokespecial #8 // Method "<init>":(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
    28: astore_1
    29: aload_1
    30: invokevirtual #9 // Method getA:()Ljava/lang/Object;
    33: checkcast #6 // class org/java/learn/generics/Automobile,get方法的转型
    36: astore_2
    37: aload_1
    38: invokevirtual #10 // Method getB:()Ljava/lang/Object;
    41: checkcast #6 // class org/java/learn/generics/Automobile,get方法的转型
    44: astore_3
    45: aload_1
    46: invokevirtual #11 // Method getC:()Ljava/lang/Object;
    49: checkcast #6 // class org/java/learn/generics/Automobile,get方法的转型
    52: astore 4
    54: return
    }

参考资料

  1. 《Java编程思想》
  2. 《Effective Java》
  3. 《Java核心技术》