Файл .class не содержит имен переменных, хотя настройка для их создания активирована

Я пытаюсь найти причину странного эффекта с файлами .class. Вроде для интерфейсов имена переменных, передаваемых в функцию, не указаны, а в классах реализации они есть. Я наткнулся на этот эффект при декомпиляции некоторых моих собственных файлов классов с помощью JD-Gui.

Я проверил это с этими двумя файлами:

Person.java

public interface Person {
    public abstract void setName( String name );
    public void setAge( int age );
}

PersonImpl.java

public class PersonImpl implements Person {
    @Override
    public void setName(String name) {
        System.out.println("This is my name: " + name);
    }
    @Override
    public void setAge(int age) {
        System.out.println("This is my age: " + age);
    }
}

JD-Gui возвращает это при декомпиляции:

Интерфейс показывает информацию только об общем типе переменных

Класс реализации показывает

Используя javap -verbose x.class, я получаю аналогичные результаты: сигнатуры печатных методов различаются от интерфейса к классу реализации. В одном отсутствуют имена переменных, как я указал их в своем источнике, в другом они есть.

Я попытался ответить на свой вопрос, изучая спецификацию виртуальной машины Java. но должен признать, что я не смог найти свой путь через этот документ.

Есть ли причина, почему это было разработано таким образом?

Изменить:

Из-за всех хороших ответов, которые я получил, я добавил несколько строк в свой интерфейс и класс реализации, чтобы подтвердить утверждения из ответа:

Person.java

default public void yawn(int count) {
        for (int i = 1; i <= count; i++)
            System.out.println("uaaaaah ....");
    }

JD-Gui умеет определять имя параметра:

JD-Gui может показывать имя переменной

JavaP может перечислить его в LocalVariableTable:

имена переменных, как показано javap

Когда я добавляю абстрактный метод в реализующий класс и делаю весь класс абстрактным (что мне нужно, потому что он содержит один абстрактный метод)...

PersonImpl.java

public abstract void setPlanet( String planet );

... то JD-Gui не может декомпилировать этот файл класса. Но, к счастью, javap все еще может сбросить файл. Все методы, которые не являются абстрактными, сохраняют свои LocalVariableTable. А у абстрактного метода есть сигнатура, но нет ни Code, ни Lines, ни даже LocalVariableTable (это и ожидалось)

дамп из javap для абстрактного метода PersonImpls


person Marged    schedule 10.06.2015    source источник
comment
Вы спрашиваете, почему параметр(ы) интерфейса (String paramString) отличается по имени от параметра(ов) реализации (String name)?   -  person Jonny Henly    schedule 10.06.2015
comment
@JonnyHenly Я пытался выразить это, да ;-)   -  person Marged    schedule 10.06.2015
comment
Это скорее предположение, чем что-либо еще, но может ли это быть связано с ключевым словом abstract? Глядя на это с точки зрения C++, можно сказать: при определении прототипа метода имя параметра не имеет значения. Также не рекомендуется использовать абстрагироваться от интерфейсов (-методов)   -  person Turing85    schedule 10.06.2015
comment
@ Turing85 Хороший вопрос. Из-за этого я добавил два метода, один с абстрактным и один без. Оба (у меня на скриншоте этого нет) пропускают имя переменной.   -  person Marged    schedule 10.06.2015
comment
Как и в комментарии @ Turing85, имена параметров интерфейса не имеют значения, важны только типы параметров. Интерфейс существует только как контракт с классом для реализации методов. Однако имена параметров класса имеют значение из-за области видимости.   -  person Jonny Henly    schedule 10.06.2015
comment
В любом случае, не все методы интерфейса являются общедоступными абстрактными?   -  person D. Ben Knoble    schedule 10.06.2015
comment
@BenKnoble, то есть было сказано в документации (посмотрите ссылку, которую я разместил)   -  person Turing85    schedule 10.06.2015
comment
@ Turing85 в следующий раз я перейду по ссылке :P   -  person D. Ben Knoble    schedule 10.06.2015
comment
Сделал еще несколько тестов. Если ваш интерфейс определяет метод default и вы выполняете javap -verbose ..., вы увидите имя переменной. Не могли бы вы проверить с помощью JD-Gui, видит ли он имя метода default?   -  person Turing85    schedule 10.06.2015
comment
@ Turing85 javap -l -c действительно показывает LocalVariableTable для методов по умолчанию, и JD-Gui, похоже, тоже это показывает.   -  person Vivin Paliath    schedule 10.06.2015
comment
@Marged abstract лишний и не рекомендуется. Определения методов неявно абстрактны в интерфейсах (если вы не предоставляете метод по умолчанию).   -  person Vivin Paliath    schedule 10.06.2015


Ответы (3)


На самом деле в самом файле класса нет ничего, что хранило бы имя параметра метода. Если вы посмотрите раздел 4.3. 3 вы увидите следующие определения для MethodDescriptor:

Дескриптор метода представляет параметры, которые принимает метод, и значение, которое он возвращает:

MethodDescriptor:
    ( ParameterDescriptor* ) ReturnDescriptor
A parameter descriptor represents a parameter passed to a method:

ParameterDescriptor:
    FieldType

Дескриптор возврата представляет собой тип значения, возвращаемого методом. Это ряд символов, сгенерированных грамматикой:

ReturnDescriptor:
    FieldType
    VoidDescriptor

VoidDescriptor:
    V

Символ V указывает на то, что метод не возвращает значения (тип возвращаемого значения — void).

Вы можете увидеть это, если распечатаете байт-код для Person.class и PersonImpl.class, используя javap -c:

Compiled from "Person.java"
public interface Person {
  public abstract void setName(java.lang.String);

  public abstract void setAge(int);
}

Compiled from "PersonImpl.java"
public class PersonImpl implements Person {
  public PersonImpl();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void setName(java.lang.String);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: ldc           #5                  // String This is my name:
      12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: aload_1
      16: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      22: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      25: return

  public void setAge(int);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: ldc           #9                  // String This is my age:
      12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: iload_1
      16: invokevirtual #10                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      22: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      25: return
}

Вы можете видеть, что подпись метода ничего не говорит об имени параметра; только его тип.

Я подозреваю, что JD-Gui, вероятно, использует какую-то эвристику, основанную на соглашениях JavaBeans, для получения имени параметра. Поскольку имя метода — setName, предполагается, что имя параметра — name. Попробуйте изменить имя параметра на другое, отличное от name, и посмотрите, что выведет JD-Gui.

Отладочная информация, такая как локальные переменные, появится, если вы компилируете с использованием -g или -g:vars; он не отображается по умолчанию. Они отображаются в атрибуте LocalVariableTable. Из раздела 4.7.13:

Атрибут LocalVariableTable — это необязательный атрибут переменной длины в таблице атрибутов атрибута Code (§4.7.3). Он может использоваться отладчиками для определения значения данной локальной переменной во время выполнения метода.

Обратите внимание на необязательную часть; вот почему вы не видите его по умолчанию. Теперь, если вы посмотрите на раздел 4.7 .3 для атрибута Code:

Атрибут Code является атрибутом переменной длины в таблице атрибутов структуры method_info (§4.6). Атрибут Code содержит инструкции виртуальной машины Java и вспомогательную информацию для метода, включая метод инициализации экземпляра или метод инициализации класса или интерфейса (§2.9).

Если метод является нативным или абстрактным, его структура method_info не должна иметь атрибута Code в таблице атрибутов. В противном случае его структура method_info должна иметь ровно один атрибут Code в своей таблице атрибутов.

Поскольку определения методов интерфейса фактически абстрактны (если только вы не используете методы по умолчанию), вы не увидите для них запись LocalVariableTable. Я использовал последнюю версию JD-Gui для PersonImpl.class, которая была не скомпилирована с -g, и обнаружила, что она не отображает name и age. Вместо этого он отображал paramString и paramInt точно так же, как вы видели для Person.class. Однако, если вы скомпилируете его с флагом -g, вы увидите name и age.

person Vivin Paliath    schedule 10.06.2015
comment
Я думаю, что JD-Gui использует LocalVariableTable для генерации имен (выполните javap -verbose Class.class, и вы увидите эту таблицу). А поскольку интерфейс не имеет реализации, у него нет LocalVariableTable. - person Turing85; 10.06.2015
comment
@ Turing85 Только при компиляции с отладочной информацией (флаг -g). В противном случае таблица не отображается, даже если вы используете флаг -l с javap. - person Vivin Paliath; 10.06.2015

Это связано с тем, что метод abstract находится в интерфейсе. Информация об именах параметров метода содержится в атрибут LocalVariableTable в байт-коде, который присутствует в атрибуте Code:

Атрибут LocalVariableTable является необязательным атрибутом переменной длины в таблице атрибутов атрибута Code (§4.7.3).

Атрибут Code определяется следующим образом:

Атрибут Code является атрибутом переменной длины в таблице атрибутов структуры method_info (§4.6). Атрибут Code содержит инструкции виртуальной машины Java и вспомогательную информацию для одного метода, метода инициализации экземпляра (§2.9) или метода инициализации класса или интерфейса (§2.9). Каждая реализация виртуальной машины Java должна распознавать атрибуты кода. Если используется метод native или abstract, его структура method_info не должна иметь атрибута Code. В противном случае его структура method_info должна иметь ровно один атрибут Code.

person M A    schedule 10.06.2015
comment
Похоже, что таблица присутствует только в том случае, если во время компиляции использовался флаг -g, и не существует по умолчанию. Затем использование -l с javap покажет таблицу. Иначе не показывает. Обратите внимание: Атрибут LocalVariableTable является необязательным атрибутом переменной длины в таблице атрибутов атрибута Code (§4.7.3). Он может использоваться отладчиками для определения значения данной локальной переменной во время выполнения метода. - person Vivin Paliath; 10.06.2015

Как объясняется в других ответах, наличие LocalVariableTable зависит от наличия атрибута Code и, следовательно, недоступно для методов abstract. Обратите внимание, что в Java 8 появился атрибут для сохранения имен параметров, который работает независимо от отладочной информации. Создание этого атрибута должно быть выбрано с помощью флага времени компиляции:

Учитывая ваш interface:

public interface Person {
    void setName(String name);
    void setAge(int age);
}
> javac Person.java

> javap -v Person
Classfile /C:/Users/pietsch/AppData/Local/Temp/Person.class
  Last modified 11.06.2015; size 159 bytes
  MD5 checksum 2fc084aa2f41b0b98e1417be7faeff8b
  Compiled from "Person.java"
public interface Person
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
   #1 = Class              #9             // Person
   #2 = Class              #10            // java/lang/Object
   #3 = Utf8               setName
   #4 = Utf8               (Ljava/lang/String;)V
   #5 = Utf8               setAge
   #6 = Utf8               (I)V
   #7 = Utf8               SourceFile
   #8 = Utf8               Person.java
   #9 = Utf8               Person
  #10 = Utf8               java/lang/Object
{
  public abstract void setName(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_ABSTRACT

  public abstract void setAge(int);
    descriptor: (I)V
    flags: ACC_PUBLIC, ACC_ABSTRACT
}
SourceFile: "Person.java"
> javac -parameters Person.java

> javap -v Person
Classfile /C:/Users/pietsch/AppData/Local/Temp/Person.class
  Last modified 11.06.2015; size 213 bytes
  MD5 checksum 63dfd86ff035e339baf7b9e9ae65020f
  Compiled from "Person.java"
public interface Person
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
   #1 = Class              #12            // Person
   #2 = Class              #13            // java/lang/Object
   #3 = Utf8               setName
   #4 = Utf8               (Ljava/lang/String;)V
   #5 = Utf8               MethodParameters
   #6 = Utf8               name
   #7 = Utf8               setAge
   #8 = Utf8               (I)V
   #9 = Utf8               age
  #10 = Utf8               SourceFile
  #11 = Utf8               Person.java
  #12 = Utf8               Person
  #13 = Utf8               java/lang/Object
{
  public abstract void setName(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_ABSTRACT
    MethodParameters:
      Name                           Flags
      name

  public abstract void setAge(int);
    descriptor: (I)V
    flags: ACC_PUBLIC, ACC_ABSTRACT
    MethodParameters:
      Name                           Flags
      age
}
SourceFile: "Person.java"

Я не знаю, может ли JD-Gui использовать эту информацию.

person Holger    schedule 11.06.2015
comment
можете ли вы сказать мне, какой это флаг времени компиляции? - person Marged; 11.06.2015
comment
Я думал, что сделал. При запуске javac в командной строке, как показано во втором выводе консоли, параметр компилятора равен -parameters. IDE могут иметь связанные переключатели, например. в Eclipse флажок помечен как «Хранить информацию о параметрах метода (можно использовать через отражение)»… - person Holger; 11.06.2015