Java Generics Type Erasure字节代码

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

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

public class Node { private T data; private Node next; public Node(T data, Node next) } this.data = data; this.next = next; } public T getData() { return data; } // ... } 

因为类型参数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; } // ... } 

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

 public class Node { private T data; private Node next; public Node(T paramT, Node paramNode) { this.data = paramT; this.next = paramNode; } public T getData() { return this.data; } } 

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

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

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

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

 public class Node { public Node(T, Node); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()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 } 

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

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

 Node.class.getTypeParameters() 

下一位代码将返回“T”。

 (new Node()).getClass().getTypeParameters()[0].getName() 

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

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

 Node node = new Node(1, null); Integer i = node.getData(); 

 Node node = new Node(1, null); Integer i = (Integer)node.getData(); 

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

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

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

  ... LocalVariableTypeTable: Start Length Slot Name Signature 0 5 0 this Ltest/Node; 

请参阅JVM规范中的此部分 :

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

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

  • 任何类或接口声明的类签名,它是通用的,或者具有参数化类型作为超类或超接口 ,或两者兼而有之。

  • 任何方法或构造函数声明的方法签名,它是通用的,或者具有类型变量或参数化类型作为返回类型或forms参数类型 ,或者在throws子句中具有类型变量,或者它们的任何组合。

大家,

我希望它只是反编译器问题,即JD-GUI

当我用不同的反编译器即JDecompiler打开时,我能够看到预期的字节码如下:

 public class Node { private Object data; private Node next; public Node(Object obj, Node node) { /* 7*/ data = obj; /* 8*/ next = node; } public Object getData() { /* 12*/ return data; } }