.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 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(这是预期的)
最佳答案:
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虚拟机实现都必须识别代码属性。 如果该方法为native
或abstract
,则其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.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
属性中。来自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} }。相反,它显示age
和paramString
,就像您在paramInt
中看到的一样。但是,如果您使用Person.class
标记进行编译,则会看到-g
和name
。
答案 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是否能够使用这些信息。