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;
            }
}