本文首发于个人网站:Java阿杜

类型擦除

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

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

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

    public Holder4(T a, T b, T c) {

    1. this.a = a;
    2. this.b = b;
    3. this.c = c;

    }

    public T getA() {

    1. return a;

    }

    public T getB() {

    1. return b;

    }

    public T getC() {

    1. return c;

    }

    public void setA(T a) {

    1. this.a = a;

    }

    public void setB(T b) {

    1. this.b = b;

    }

    public void setC(T c) {

    1. this.c = c;

    }

  1. public static void main(String[] args) {
  2. Holder4<Automobile> holder4 = new Holder4<>(new Automobile(),new Automobile(), new Automobile());
  3. Automobile a = holder4.getA(); //编译器帮忙转型,不需要显式转型
  4. Automobile b = holder4.getB();
  5. Automobile c = holder4.getC();
  6. }

}

  1. <br />在Java中,每定义一个泛型类型,就会自动提供一个对应的原始类型,例如:
  2. ```java
  3. public class Holder4Raw {
  4. private Object a;
  5. private Object b;
  6. private Object c;
  7. public Holder4Raw(Object a, Object b, Object c) {
  8. this.a = a;
  9. this.b = b;
  10. this.c = c;
  11. }
  12. public Object getA() {
  13. return a;
  14. }
  15. public Object getB() {
  16. return b;
  17. }
  18. public Object getC() {
  19. return c;
  20. }
  21. public void setA(Object a) {
  22. this.a = a;
  23. }
  24. public void setB(Object b) {
  25. this.b = b;
  26. }
  27. public void setC(Object c) {
  28. this.c = c;
  29. }
  30. public static void main(String[] args) {
  31. Holder4Raw holder4Raw = new Holder4Raw(new Automobile(),new Automobile(), new Automobile());
  32. Automobile a = (Automobile) holder4Raw.getA(); //显示的转型
  33. Automobile b = (Automobile) holder4Raw.getB();
  34. Automobile c = (Automobile) holder4Raw.getC();
  35. }
  36. }
  1. 为什么选择这种实现机制?

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

    1. public class org.java.learn.generics.Holder4<T> {
    2. public org.java.learn.generics.Holder4(T, T, T);
    3. Code:
    4. 0: aload_0
    5. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
    6. 4: aload_0
    7. 5: aload_1
    8. 6: putfield #2 // Field a:Ljava/lang/Object;
    9. 9: aload_0
    10. 10: aload_2
    11. 11: putfield #3 // Field b:Ljava/lang/Object;
    12. 14: aload_0
    13. 15: aload_3
    14. 16: putfield #4 // Field c:Ljava/lang/Object;
    15. 19: return
    16. public T getA();
    17. Code:
    18. 0: aload_0
    19. 1: getfield #2 // Field a:Ljava/lang/Object;
    20. 4: areturn
    21. public T getB();
    22. Code:
    23. 0: aload_0
    24. 1: getfield #3 // Field b:Ljava/lang/Object;
    25. 4: areturn
    26. public T getC();
    27. Code:
    28. 0: aload_0
    29. 1: getfield #4 // Field c:Ljava/lang/Object;
    30. 4: areturn
    31. public void setA(T);
    32. Code:
    33. 0: aload_0
    34. 1: aload_1
    35. 2: putfield #2 // Field a:Ljava/lang/Object;
    36. 5: return
    37. public void setB(T);
    38. Code:
    39. 0: aload_0
    40. 1: aload_1
    41. 2: putfield #3 // Field b:Ljava/lang/Object;
    42. 5: return
    43. public void setC(T);
    44. Code:
    45. 0: aload_0
    46. 1: aload_1
    47. 2: putfield #4 // Field c:Ljava/lang/Object;
    48. 5: return
    49. public static void main(java.lang.String[]);
    50. Code:
    51. 0: new #5 // class org/java/learn/generics/Holder4
    52. 3: dup
    53. 4: new #6 // class org/java/learn/generics/Automobile
    54. 7: dup
    55. 8: invokespecial #7 // Method org/java/learn/generics/Automobile."<init>":()V
    56. 11: new #6 // class org/java/learn/generics/Automobile
    57. 14: dup
    58. 15: invokespecial #7 // Method org/java/learn/generics/Automobile."<init>":()V
    59. 18: new #6 // class org/java/learn/generics/Automobile
    60. 21: dup
    61. 22: invokespecial #7 // Method org/java/learn/generics/Automobile."<init>":()V
    62. 25: invokespecial #8 // Method "<init>":(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
    63. 28: astore_1
    64. 29: aload_1
    65. 30: invokevirtual #9 // Method getA:()Ljava/lang/Object;
    66. 33: checkcast #6 // class org/java/learn/generics/Automobile,get方法的转型
    67. 36: astore_2
    68. 37: aload_1
    69. 38: invokevirtual #10 // Method getB:()Ljava/lang/Object;
    70. 41: checkcast #6 // class org/java/learn/generics/Automobile,get方法的转型
    71. 44: astore_3
    72. 45: aload_1
    73. 46: invokevirtual #11 // Method getC:()Ljava/lang/Object;
    74. 49: checkcast #6 // class org/java/learn/generics/Automobile,get方法的转型
    75. 52: astore 4
    76. 54: return
    77. }

参考资料

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

本号专注于后端技术、JVM问题排查和优化、Java面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。
Java泛型基础(四):类型擦除揭秘 - 图1