【问题标题】:.class file contains no variable names although setting to create those is activated.class 文件不包含变量名,尽管创建这些变量的设置已激活
【发布时间】:2015-08-26 06:59:50
【问题描述】:

我试图找出 .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 Virtual Machine Specification 的问题,但我不得不承认我没能通过这份文档找到自己的方法。

这样设计有什么原因吗?

编辑:

由于我收到了所有好的答案,我在接口和实现类中添加了一些行,以支持答案中的陈述:s

Person.java

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

JD-Gui能够判断参数名称:

JavaP 能够在 LocalVariableTable 中列出它:

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

PersonImpl.java

public abstract void setPlanet( String planet );

... 那么 JD-Gui 无法反编译这个类文件。但幸运的是 javap 仍然能够转储文件。所有非抽象的方法都保留它们的LocalVariableTable。抽象方法有签名,但既没有 Code,也没有 Lines,甚至没有 LocalVariableTable(这是预期的)

【问题讨论】:

  • 您是否在问为什么接口参数 (String paramString) 与实现参数 (String name) 的名称不同?
  • @JonnyHenly 我试图表达这一点,是的 ;-)
  • 这更像是一个猜测,但可能是由于 abstract 关键字?从 C++ 的角度来看,可以说:定义方法原型时,参数的名称无关紧要。也是discouraged to use abstract on interface(-method)s
  • @Turing85 好点。因此,我添加了两种方法,一种带有“抽象”,一种没有。两者(我在屏幕截图上没有这个)都错过了变量名。
  • @Turing85 javap -l -c 确实显示了默认方法的LocalVariableTable,JD-Gui 似乎也显示了它。

标签: java bytecode javap jd-gui


【解决方案1】:

在类文件本身中实际上没有任何东西存储方法参数的名称。如果您查看section 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)。

如果您使用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

LocalVariableTable 属性是Code 属性(第 4.7.3 节)的属性表中的可选可变长度属性。调试器可以使用它来确定方法执行期间给定局部变量的值。

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

Code 属性是 method_info 结构的属性表中的可变长度属性(第 4.6 节)。 Code 属性包含 Java 虚拟机指令和方法的辅助信息,包括实例初始化方法或类或接口初始化方法(第 2.9 节)。

如果方法是本机或抽象方法,则其method_info 结构在其属性表中不得有Code 属性。 否则,其method_info 结构必须恰好有一个Code 属性在其属性表中。

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

【讨论】:

  • 我认为 JD-Gui 正在使用LocalVariableTable 来生成名称(执行javap -verbose Class.class,您将看到此表)。而且由于一个接口没有实现,它没有LocalVariableTable
  • @Turing85 仅当您使用调试信息进行编译时(-g 标志)。否则,即使您将-l 标志与javap 一起使用,表格也不会显示。
【解决方案2】:

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

LocalVariableTable 属性是Code (§4.7.3) 属性的属性表中的可选可变长度属性。

Code属性定义如下:

Code 属性是method_info(第 4.6 节)结构的属性表中的可变长度属性。代码属性包含单个方法、实例初始化方法(第 2.9 节)或类或接口初始化方法(第 2.9 节)的 Java 虚拟机指令和辅助信息。每个 Java 虚拟机实现都必须识别代码属性。 如果方法是nativeabstract,则其method_info 结构不得具有Code 属性。 否则,其method_info 结构必须具有正好有一个Code 属性。

【讨论】:

  • 似乎只有在编译期间使用了-g 标志时该表才存在,并且默认情况下不存在。然后使用-ljavap 将显示表格。否则不显示。请注意:“LocalVariableTable 属性是代码属性的属性表中的一个可选 可变长度属性(第 4.7.3 节)。调试器可以使用它来确定在方法执行期间给定局部变量的值。”
【解决方案3】:

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

【讨论】:

  • 你能告诉我这是哪个编译时标志吗?
  • 我以为我做到了。在命令行上运行javac 时,如我的第二个控制台输出所示,编译器选项为-parameters。 IDE 可能有相关的切换,例如在 Eclipse 中,该复选框被标记为“存储有关方法参数的信息(可通过反射使用)”......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-24
  • 2018-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多