Java Generics Type Erasure byte code
Согласно документации java на Erasure of Generic Types,
Рассмотрим следующий общий класс, который представляет node в отдельно связанном списке:
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 в качестве декомпилятора для анализа байтового кода
Ответы
Ответ 1
Байт-код содержит метаинформацию о самом коде, такую как общие типы (или имена переменных) - это не означает, что он может использоваться 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
}
Вы можете видеть, что существуют методы и аргументы общих типов, но сам код относится к объекту, как ожидалось из-за процесса стирания.
Ответ 2
Общая информация типа сохраняется в байт-коде, в частности, в информации подписи членов класса.
Например, выполнение javap -verbose Node.class
дает:
...
LocalVariableTypeTable:
Start Length Slot Name Signature
0 5 0 this Ltest/Node<TT;>;
Смотрите этот раздел из спецификации JVM:
Подписи кодируют объявления, написанные на языке программирования Java, которые используют типы вне системы типов виртуальной машины Java. Они поддерживают отражение и отладку, а также компиляцию, когда доступны только файлы классов.
Компилятор Java должен выдавать подпись для любого класса, интерфейса, конструктора, метода или поля, в объявлении которого используются переменные типа или параметризованные типы. В частности, компилятор Java должен испускать:
-
Подпись класса для любого объявления класса или интерфейса, которое является либо общим, либо имеет параметризованный тип в качестве суперкласса или суперинтерфейса, либо и то, и другое.
-
Подпись метода для любого объявления метода или конструктора, которое является либо общим, либо имеет переменную типа или параметризованный тип в качестве типа возвращаемого значения или формальный тип параметра, либо имеет переменную типа в предложении throws или любую их комбинацию.
Ответ 3
Сохраняется факт, что класс является общим. Например, во время выполнения вы можете вызвать
Node.class.getTypeParameters()
Следующий бит кода вернет "T".
(new Node<Integer>()).getClass().getTypeParameters()[0].getName()
Вы не можете получить значение параметров типа во время выполнения, но JVM знает, что они там.
Erasure вступает в игру при создании экземпляра.
Node<Integer> node = new Node<Integer>(1, null);
Integer i = node.getData();
становится
Node node = new Node(1, null);
Integer i = (Integer)node.getData();
Общие классы всегда являются общими. Но экземпляры не содержат информацию о родовых типах внутри них. Компилятор проверяет, что все, что вы сделали, совпадает с общим типом, а затем вставляет приведения.
Ответ 4
Все,
Надеюсь, что это проблема декомпилятора только i.e. 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;
}
}