Java Generics Type Erasure字节代码

Baj*_*aik 11 java generics type-erasure

根据关于Erasure of Generic Types的java文档,

考虑以下表示单链表中节点的泛型类:

public class Node<T> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) }
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}
Run Code Online (Sandbox Code Playgroud)

因为类型参数T是无界的,所以Java编译器将其替换为Object:

public class Node {

    private Object data;
    private Node next;

    public Node(Object data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Object getData() { return data; }
    // ...
}
Run Code Online (Sandbox Code Playgroud)

但是在使用Java 1.7.0_11编译之后,当我用任何反编译器打开它时,我可以看到与源代码相同的代码.

public class Node<T>
{
  private T data;
  private Node<T> next;

  public Node(T paramT, Node<T> paramNode)
  {
    this.data = paramT;
    this.next = paramNode;
  }

  public T getData()
  {
    return this.data;
  }
}
Run Code Online (Sandbox Code Playgroud)

如果在编译时应用Type-Erasure,则字节代码不得包含如上所示的通用信息.请澄清我.

注意:我使用JD-GUI作为反编译器来分析字节码

ass*_*ias 6

字节码包含有关代码本身的元信息,例如泛型类型(或变量名称) - 它并不意味着JVM可以使用它.

你的类的反汇编字节码如下所示(你可以看到它javap -c Node.class):

public class Node<T> {
  public Node(T, Node<T>);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: aload_1
       6: putfield      #2                  // Field data:Ljava/lang/Object;
       9: aload_0
      10: aload_2
      11: putfield      #3                  // Field next:LNode;
      14: return

  public T getData();
    Code:
       0: aload_0
       1: getfield      #2                  // Field data:Ljava/lang/Object;
       4: areturn
}
Run Code Online (Sandbox Code Playgroud)

您可以看到方法和参数泛型类型存在,但由于擦除过程,代码本身会按预期引用Object.


man*_*uti 5

通用类型信息仍然保存在字节码中,特别是类成员的签名信息中。

例如,执行javap -verbose Node.class产量:

 ...
 LocalVariableTypeTable:
      Start  Length  Slot  Name   Signature
          0       5     0  this   Ltest/Node<TT;>;
Run Code Online (Sandbox Code Playgroud)

请参阅JVM 规范中的此部分

签名对用 Java 编程语言编写的声明进行编码,这些声明使用 Java 虚拟机类型系统之外的类型。它们支持反射和调试,以及只有类文件可用时的编译。

Java 编译器必须为其声明使用类型变量或参数化类型的任何类、接口、构造函数、方法或字段发出签名。具体来说,Java 编译器必须发出:

  • 任何类或接口声明的类签名,该声明要么是泛型的,要么具有作为超类或超接口或两者的参数化类型。

  • 任何方法或构造函数声明的方法签名,该声明可以是泛型的,或者具有类型变量或参数化类型作为返回类型或形式参数类型,或者在 throws 子句中具有类型变量,或其任意组合。


axb*_*unt 5

保留了类是通用的这一事实.例如,在运行时您可以调用

Node.class.getTypeParameters()
Run Code Online (Sandbox Code Playgroud)

下一位代码将返回"T".

(new Node<Integer>()).getClass().getTypeParameters()[0].getName()
Run Code Online (Sandbox Code Playgroud)

您无法在运行时获取类型参数的值,但JVM知道它们在那里.

构造实例时,擦除会发挥作用.

Node<Integer> node = new Node<Integer>(1, null);
Integer i = node.getData();
Run Code Online (Sandbox Code Playgroud)

Node node = new Node(1, null);
Integer i = (Integer)node.getData();
Run Code Online (Sandbox Code Playgroud)

通用类总是通用的.但是实例不会在其中包含泛型类型信息.编译器会验证您所做的所有事情是否与泛型类型一致,然后插入强制转换.