4

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

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

人.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 虚拟机规范的问题,但不得不承认我未能通过本文档找到方法。

这样设计有什么原因吗?

编辑:

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

人.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 抽象方法

4

3 回答 3

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.classPersonImpl.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如果您使用or进行编译,将显示调试信息,例如局部变量-g:vars;默认情况下不显示。这些显示在LocalVariableTable属性中。从第 4.7.13 节开始

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

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

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

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

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

于 2015-06-10T20:21:24.043 回答
3

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

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

Code属性定义如下:

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

于 2015-06-10T20:24:03.940 回答
0

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

于 2015-06-11T14:14:58.087 回答