.class 文件不包含变量名称,尽管创建这些变量的设置已激活

.class file contains no variable names although setting to create those is activated

我试图找出 .class 文件出现奇怪效果的原因。对于接口来说,似乎没有列出传递给函数的变量的名称,但在实现中 classes 它们是。我在使用 JD-Gui 反编译我自己的一些 class 文件时偶然发现了这种效果。

我用这两个文件检查了这个:

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 returns反编译时的这个:

使用 javap -verbose x.class 我得到了类似的结果:打印的方法签名与实现 class 的接口不同。一个遗漏了我在源代码中指定的变量名称,另一个有它们。

我试图通过研究 Java Virtual Machine Specification 来回答我的问题,但不得不承认我没能找到阅读本文档的方法。

这样设计有什么原因吗?

编辑:

由于收到了所有好的答案,我在界面和实现中添加了一些行 class 以支持 answer:s

中的语句

Person.java

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

JD-Gui 可以判断参数名称:

JavaP 能够在 LocalVariableTable 中列出它:

当我向实现 class 添加一个抽象方法并使整个 class 抽象(我需要这样做,因为它包含一个抽象方法)...

PersonImpl.java

public abstract void setPlanet( String planet );

...然后JD-Gui 无法反编译这个class 文件。但幸运的是 javap 仍然能够转储文件。 非抽象 的所有方法都保留其 LocalVariableTable。并且抽象方法有一个签名,但既没有代码,也没有行,甚至没有 LocalVariableTable(这是预期的)

实际上 class 文件本身并没有存储方法参数名称的内容。如果您查看 section 4.3.3,您将看到 MethodDescriptor 的以下定义:

A method descriptor represents the parameters that the method takes and the value that it returns:

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

ParameterDescriptor:
    FieldType

A return descriptor represents the type of the value returned from a method. It is a series of characters generated by the grammar:

ReturnDescriptor:
    FieldType
    VoidDescriptor

VoidDescriptor:
    V

The character V indicates that the method returns no value (its return type is void).

如果使用 javap -c:

打印出 Person.classPersonImpl.class 的字节码,您可以看到这一点
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 属性中。来自 section 4.7.13:

The LocalVariableTable attribute is an optional variable-length attribute in the attributes table of a Code attribute (§4.7.3). It may be used by debuggers to determine the value of a given local variable during the execution of a method.

注意可选部分;这就是默认情况下您看不到它的原因。现在,如果您查看 section 4.7.3Code 属性:

The Code attribute is a variable-length attribute in the attributes table of a method_info structure (§4.6). A Code attribute contains the Java Virtual Machine instructions and auxiliary information for a method, including an instance initialization method or a class or interface initialization method (§2.9).

If the method is either native or abstract, its method_info structure must not have a Code attribute in its attributes table. Otherwise, its method_info structure must have exactly one Code attribute in its attributes table.

由于接口方法定义实际上是抽象的(除非您使用 default methods),您不会看到它们的 LocalVariableTable 条目。我用最新版的JD-Gui对付not-g编译的PersonImpl.class,发现没有显示nameage。相反,它显示 paramStringparamInt,就像您在 Person.class 中看到的一样。但是,如果您使用 -g 标志编译它,您将看到 nameage

这是由于接口中的方法abstract。有关方法参数名称的信息包含在字节码中的 LocalVariableTable attribute 中,该字节码存在于 Code 属性中:

The LocalVariableTable attribute is an optional variable-length attribute in the attributes table of a Code (§4.7.3) attribute.

Code属性定义如下:

The Code attribute is a variable-length attribute in the attributes table of a method_info (§4.6) structure. A Code attribute contains the Java Virtual Machine instructions and auxiliary information for a single method, instance initialization method (§2.9), or class or interface initialization method (§2.9). Every Java Virtual Machine implementation must recognize Code attributes. If the method is either native or abstract, its method_info structure must not have a Code attribute. Otherwise, its method_info structure must have exactly one Code attribute.

正如其他答案所解释的那样,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是否可以使用这些信息。