.class文件不包含变量名称,尽管激活了创建它们的设置

.class文件不包含变量名称,尽管激活了创建它们的设置,第1张

我试图找到.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在反编译时返回:

.class文件不包含变量名称,尽管激活了创建它们的设置,Interface shows only generic variable type info,第2张

.class文件不包含变量名称,尽管激活了创建它们的设置,Implementing class shows 'real' variable name,第3张

使用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能够确定参数的名称:

.class文件不包含变量名称,尽管激活了创建它们的设置,JD-Gui is able to show the name of the variable,第4张

JavaP能够在LocalVariableTable中列出它:

.class文件不包含变量名称,尽管激活了创建它们的设置,variable names as shown by javap,第5张

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

PersonImpl.java

public abstract void setPlanet( String planet );

...然后JD-Gui无法反编译此类文件。但幸运的是,javap仍然可以转储该文件。所有非抽象的方法都保留其LocalVariableTable。而抽象方法有一个签名,但既没有Code,也没有Lines甚至是LocalVariableTable(这是预期的)

.class文件不包含变量名称,尽管激活了创建它们的设置,dump from javap for PersonImpls abstract method,第6张

最佳答案:

3 个答案:

答案 0 :(得分:3)

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

  

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

Code属性定义如下:

  

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

答案 1 :(得分:3)

类文件本身实际上没有存储方法参数名称的内容。如果您查看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)。

如果使用Person.class打印出PersonImpl.classjavap -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属性中。来自section 4.7.13:

  

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

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

  

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

     

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

由于接口方法定义实际上是抽象的(除非您使用default methods),因此您将看不到LocalVariableTable条目。我使用最新版本的JD-Gui对PersonImpl.class 编译了-g,发现它没有显示name和{{1} }。相反,它显示ageparamString,就像您在paramInt中看到的一样。但是,如果您使用Person.class标记进行编译,则会看到-gname

答案 2 :(得分:0)

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

本文经用户投稿或网站收集转载,如有侵权请联系本站。

发表评论

0条回复