.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.class
和 PersonImpl.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.3 的 Code
属性:
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
,发现没有显示name
和age
。相反,它显示 paramString
和 paramInt
,就像您在 Person.class
中看到的一样。但是,如果您使用 -g
标志编译它,您将看到 name
和 age
。
这是由于接口中的方法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是否可以使用这些信息。
我试图找出 .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.class
和 PersonImpl.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 aCode
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.3 的 Code
属性:
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 aCode
attribute in its attributes table. Otherwise, its method_info structure must have exactly oneCode
attribute in its attributes table.
由于接口方法定义实际上是抽象的(除非您使用 default methods),您不会看到它们的 LocalVariableTable
条目。我用最新版的JD-Gui对付not用-g
编译的PersonImpl.class
,发现没有显示name
和age
。相反,它显示 paramString
和 paramInt
,就像您在 Person.class
中看到的一样。但是,如果您使用 -g
标志编译它,您将看到 name
和 age
。
这是由于接口中的方法abstract
。有关方法参数名称的信息包含在字节码中的 LocalVariableTable
attribute 中,该字节码存在于 Code
属性中:
The
LocalVariableTable
attribute is an optional variable-length attribute in the attributes table of aCode
(§4.7.3) attribute.
Code
属性定义如下:
The
Code
attribute is a variable-length attribute in the attributes table of amethod_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 eithernative
orabstract
, itsmethod_info
structure must not have aCode
attribute. Otherwise, itsmethod_info
structure must have exactly oneCode
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是否可以使用这些信息。