根据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; }
// ...
}
因为类型参数 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<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;
}
}
如果在编译时应用Type-Erasure,则字节代码不得包含如上所示的通用信息。请澄清一下。
注意:我使用JD-GUI作为反编译器来分析字节码
答案 0 :(得分: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
}
您可以看到方法和参数泛型类型存在,但由于擦除过程,代码本身会按预期引用Object。
答案 1 :(得分:5)
保留了类是通用的这一事实。例如,在运行时,您可以调用
Node.class.getTypeParameters()
下一段代码将返回&#34; T&#34;。
(new Node<Integer>()).getClass().getTypeParameters()[0].getName()
您无法在运行时获取类型参数的值,但JVM知道他们在那里。
构建实例时,擦除就会发挥作用。
Node<Integer> node = new Node<Integer>(1, null);
Integer i = node.getData();
变为
Node node = new Node(1, null);
Integer i = (Integer)node.getData();
通用类总是通用的。但是实例不会在其中包含泛型类型信息。编译器会验证您所做的所有事情是否与泛型类型一致,然后插入强制转换。
答案 2 :(得分:3)
通用类型信息仍保存在字节码中,特别是在类成员的签名信息中。
例如,执行javap -verbose Node.class
会产生:
...
LocalVariableTypeTable:
Start Length Slot Name Signature
0 5 0 this Ltest/Node<TT;>;
请参阅this section from the JVM specification:
签名编码用Java编程语言编写的声明,这些声明使用Java虚拟机类型系统之外的类型。它们支持反射和调试,以及只有类文件可用时的编译。
Java编译器必须为其声明使用类型变量或参数化类型的任何类,接口,构造函数,方法或字段发出签名。具体来说,Java编译器必须发出:
任何类或接口声明的类签名,它是泛型,或者参数化类型为超类或超接口,或两者兼而有之。
任何方法或构造函数声明的方法签名,它是泛型,或者具有类型变量或参数化类型作为返回类型或形式参数类型,或者具有类型变量投掷条款或其任何组合。
答案 3 :(得分:0)
每个人,
我希望它只是反编译器问题,即 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;
}
}