ASM:(5)ClassVisitor和ClassWriter
原文:https://lsieun.github.io/java-asm-01/class-visitor-intro.html
ClassVisitor类
在ASM Core API中,最重要的三个类就是ClassReader、ClassVisitor和ClassWriter类。在进行Class Generation操作的时候,ClassVisitor和ClassWriter这两个类起着重要作用,而并不需要ClassReader类的参与。在本文当中,我们将对ClassVisitor类进行介绍。

class info
第一个部分,ClassVisitor是一个抽象类。 由于ClassVisitor类是一个abstract类,所以不能直接使用new关键字创建ClassVisitor对象。
public abstract class ClassVisitor {
}
同时,由于ClassVisitor类是一个abstract类,要想使用它,就必须有具体的子类来继承它。
第一个比较常见的ClassVisitor子类是ClassWriter类,属于Core API:
public class ClassWriter extends ClassVisitor {
}
第二个比较常见的ClassVisitor子类是ClassNode类,属于Tree API:
public class ClassNode extends ClassVisitor {
}
fields
第二个部分,ClassVisitor类定义的字段有哪些。
public abstract class ClassVisitor {
protected final int api;
protected ClassVisitor cv;
}
api字段:它是一个int类型的数据,指出了当前使用的ASM版本,其可取值为Opcodes.ASM4~Opcodes.ASM9。我们使用的ASM版本是9.0,因此我们在给api字段赋值的时候,选择Opcodes.ASM9就可以了。cv字段:它是一个ClassVisitor类型的数据,它的作用是将多个ClassVisitor串连起来。

constructors
第三个部分,ClassVisitor类定义的构造方法
public abstract class ClassVisitor {
public ClassVisitor(final int api) {
this(api, null);
}
public ClassVisitor(final int api, final ClassVisitor classVisitor) {
this.api = api;
this.cv = classVisitor;
}
}
methods
在ASM当中,使用到了Visitor Pattern(访问者模式),所以ClassVisitor当中许多的visitXxx()方法。
虽然,在ClassVisitor类当中,有许多visitXxx()方法,但是,我们只需要关注这4个方法:visit()、visitField()、visitMethod()和visitEnd()。
为什么只关注这4个方法呢?因为这4个方法是ClassVisitor类的精髓或骨架,在这个“骨架”的基础上,其它的visitXxx()都容易扩展;同时,将visitXxx()方法缩减至4个,也能减少学习过程中的认知负担,学起来更容易。
public abstract class ClassVisitor {
public void visit(
final int version,
final int access,
final String name,
final String signature,
final String superName,
final String[] interfaces);
public FieldVisitor visitField( // 访问字段
final int access,
final String name,
final String descriptor,
final String signature,
final Object value);
public MethodVisitor visitMethod( // 访问方法
final int access,
final String name,
final String descriptor,
final String signature,
final String[] exceptions);
public void visitEnd();
// ......
}
在ClassVisitor的visit()方法、visitField()方法和visitMethod()方法中都带有signature参数。这个signature参数与“泛型”密切相关;换句话说,如果处理的是一个带有泛型信息的类、字段或方法,那么就需要给signature参数提供一定的值;如果处理的类、字段或方法不带有“泛型”信息,那么将signature参数设置为null就可以了。
如果大家对signature参数感兴趣,我们可以使用之前介绍的PrintASMCodeCore类去打印一下某个泛型类的ASM代码。例如,java.lang.Comparable是一个泛型接口,我们就可以使用PrintASMCodeCore类来打印一下它的ASM代码,从来查看signature参数的值是什么。
方法的调用顺序
在ClassVisitor类当中,定义了多个visitXxx()方法。这些visitXxx()方法,遵循一定的调用顺序(可参考API文档):
visit
[visitSource][visitModule][visitNestHost][visitPermittedSubclass][visitOuterClass]
(
visitAnnotation |
visitTypeAnnotation |
visitAttribute
)*
(
visitNestMember |
visitInnerClass |
visitRecordComponent |
visitField |
visitMethod
)*
visitEnd
其中,涉及到一些符号,它们的含义如下:
[]: 表示最多调用一次,可以不调用,但最多调用一次。()和|: 表示在多个方法之间,可以选择任意一个,并且多个方法之间不分前后顺序。*: 表示方法可以调用0次或多次。
在本次课程当中,我们只关注ClassVisitor类当中的visit()方法、visitField()方法、visitMethod()方法和visitEnd()方法这4个方法,所以上面的方法调用顺序可以简化如下:
visit
(
visitField |
visitMethod
)*
visitEnd
也就是说,先调用visit()方法,接着调用visitField()方法或visitMethod()方法,最后调用visitEnd()方法。
┌─── visit()
│
├─── visitField()
ClassVisitor ───┤
├─── visitMethod()
│
└─── visitEnd()
visitXxx()方法与ClassFile
visit()方法
ClassVisitor的visitXxx()方法与ClassFile之间存在对应关系:
ClassVisitor.visitXxx() --- .class --- ClassFile
在ClassVisitor中定义的visitXxx()方法,并不是凭空产生的,这些方法存在的目的就是为了生成一个合法的.class文件,而这个.class文件要符合ClassFile的结构,所以这些visitXxx()方法与ClassFile的结构密切相关。
public void visit(
final int version,
final int access,
final String name,
final String signature,
final String superName,
final String[] interfaces);
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
| ClassVisitor方法 | 参数 | ClassFile |
|---|---|---|
ClassVisitor.visit() |
version |
minor_version和major_version |
access |
access_flags |
|
name |
this_class |
|
signature |
attributes的一部分信息 |
|
superName |
super_class |
|
interfaces |
interfaces_count和interfaces |
|
ClassVisitor.visitField() |
field_info |
|
ClassVisitor.visitMethod() |
method_info |
visitField()方法
public FieldVisitor visitField( // 访问字段
final int access,
final String name,
final String descriptor,
final String signature,
final Object value);
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
| ClassVisitor方法 | 参数 | field_info |
|---|---|---|
ClassVisitor.visitField() |
access |
access_flags |
name |
name_index |
|
descriptor |
descriptor_index |
|
signature |
attributes的一部分信息 |
|
value |
attributes的一部分信息 |
ClassWriter类
ClassWriter的父类是ClassVisitor,因此ClassWriter类继承了visit()、visitField()、visitMethod()和visitEnd()等方法。
fields
public class ClassWriter extends ClassVisitor {
private int version;
private final SymbolTable symbolTable;
private int accessFlags;
private int thisClass;
private int superClass;
private int interfaceCount;
private int[] interfaces;
private FieldWriter firstField;
private FieldWriter lastField;
private MethodWriter firstMethod;
private MethodWriter lastMethod;
private Attribute firstAttribute;
//......
}
这些字段与ClassFile结构密切相关:
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
constructors
第三个部分,就是ClassWriter定义的构造方法。
ClassWriter定义的构造方法有两个,这里只关注其中一个,也就是只接收一个int类型参数的构造方法。在使用new关键字创建ClassWriter对象时,推荐使用COMPUTE_FRAMES参数。
public class ClassWriter extends ClassVisitor {
/* A flag to automatically compute the maximum stack size and the maximum number of local variables of methods. */
public static final int COMPUTE_MAXS = 1;
/* A flag to automatically compute the stack map frames of methods from scratch. */
public static final int COMPUTE_FRAMES = 2;
// flags option can be used to modify the default behavior of this class.
// Must be zero or more of COMPUTE_MAXS and COMPUTE_FRAMES.
public ClassWriter(final int flags) {
this(null, flags);
}
}
小总结:
COMPUTE_MAXS: 计算max stack和max local信息。COMPUTE_FRAMES: 既计算stack map frame信息,又计算max stack和max local信息。
换句话说,COMPUTE_FRAMES是功能最强大的:
COMPUTE_FRAMES = COMPUTE_MAXS + stack map frame
methods
visitXxx()方法
在ClassWriter这个类当中,我们仍然是只关注其中的visit()方法、visitField()方法、visitMethod()方法和visitEnd()方法。
这些visitXxx()方法的调用,就是在为构建ClassFile提供“原材料”的过程。
toByteArray()方法
在ClassWriter类当中,提供了一个toByteArray()方法。这个方法的作用是将“所有的努力”(对visitXxx()的调用)转换成byte[],而这些byte[]的内容就遵循ClassFile结构。
在toByteArray()方法的代码当中,通过三个步骤来得到byte[]:
- 第一步,计算
size大小。这个size就是表示byte[]的最终的长度是多少。 - 第二步,将数据填充到
byte[]当中。 - 第三步,将
byte[]数据返回。
如何使用ClassWriter类
使用ClassWriter生成一个Class文件,可以大致分成三个步骤:
- 第一步,创建
ClassWriter对象。 - 第二步,调用
ClassWriter对象的visitXxx()方法。 - 第三步,调用
ClassWriter对象的toByteArray()方法。
示例代码如下:
import org.objectweb.asm.ClassWriter;
import static org.objectweb.asm.Opcodes.*;
public class HelloWorldGenerateCore {
public static byte[] dump () throws Exception {
// (1) 创建ClassWriter对象
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// (2) 调用visitXxx()方法
cw.visit();
cw.visitField();
cw.visitMethod();
cw.visitEnd(); // 注意,最后要调用visitEnd()方法
// (3) 调用toByteArray()方法
byte[] bytes = cw.toByteArray();
return bytes;
}
}
创建ClassWriter对象
推荐使用COMPUTE_FRAMES
在创建ClassWriter对象的时候,要指定一个flags参数,它可以选择的值有三个:
- 第一个,可以选取的值是
0。ASM不会自动计算max stacks和max locals,也不会自动计算stack map frames。 - 第二个,可以选取的值是
ClassWriter.COMPUTE_MAXS。ASM会自动计算max stacks和max locals,但不会自动计算stack map frames。 - 第三个,可以选取的值是
ClassWriter.COMPUTE_FRAMES(推荐使用)。ASM会自动计算max stacks和max locals,也会自动计算stack map frames。
┌──────────────────────┬─────────────────────────────────┬────────────────────────┐
│ flags │ max stacks and max locals │ stack map frames │
├──────────────────────┼─────────────────────────────────┼────────────────────────┤
│ 0 │ NO │ NO │
├──────────────────────┼─────────────────────────────────┼────────────────────────┤
│ COMPUTE_MAXS │ YES │ NO │
├──────────────────────┼─────────────────────────────────┼────────────────────────┤
│ COMPUTE_FRAMES │ YES │ YES │
└──────────────────────┴─────────────────────────────────┴────────────────────────┘
创建ClassWriter对象,如下所示:
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
为什么推荐使用COMPUTE_FRAMES
在创建ClassWriter对象的时候,使用ClassWriter.COMPUTE_FRAMES,ASM会自动计算max stacks和max locals,也会自动计算stack map frames。
首先,来看一下max stacks和max locals。在ClassFile结构中,每一个方法都用method_info来表示,而方法里定义的代码则使用Code属性来表示,其结构如下:
Code_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack; // 这里是max stacks
u2 max_locals; // 这里是max locals
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
如果我们在创建ClassWriter(flags)对象的时候,将flags参数设置为ClassWriter.COMPUTE_MAXS或ClassWriter.COMPUTE_FRAMES,那么ASM会自动帮助我们计算Code结构中max_stack和max_locals的值。
接着,来看一下stack map frames。在Code结构里,可能有多个attributes,其中一个可能就是StackMapTable_attribute。StackMapTable_attribute结构,就是stack map frame具体存储格式,它的主要作用是对ByteCode进行类型检查。
StackMapTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_entries;
stack_map_frame entries[number_of_entries];
}
如果我们在创建ClassWriter(flags)对象的时候,将flags参数设置为ClassWriter.COMPUTE_FRAMES,那么ASM会自动帮助我们计算StackMapTable_attribute的内容。

我们推荐使用ClassWriter.COMPUTE_FRAMES。因为ClassWriter.COMPUTE_FRAMES这个选项,能够让ASM帮助我们自动计算max stacks、max locals和stack map frame的具体内容。
- 如果将
flags参数的取值为0,那么我们就必须要提供正确的max stacks、max locals和stack map frame的值; - 如果将
flags参数的取值为ClassWriter.COMPUTE_MAXS,那么ASM会自动帮助我们计算max stacks和max locals,而我们则需要提供正确的stack map frame的值。
那么,ASM为什么会提供0和ClassWriter.COMPUTE_MAXS这两个选项呢?因为ASM在计算这些值的时候,要考虑各种各样不同的情况,所以它的算法相对来说就比较复杂,因而执行速度也会相对较慢。同时,ASM也鼓励开发者去研究更好的算法;如果开发者有更好的算法,就可以不去使用ClassWriter.COMPUTE_FRAMES,这样就能让程序的执行效率更高效。
但是,不得不说,要想计算max stacks、max locals和stack map frames,也不是一件容易的事情。出于方便的目的,就推荐大家使用ClassWriter.COMPUTE_FRAMES。在大多数情况下,ClassWriter.COMPUTE_FRAMES都能帮我们计算出正确的值。在少数情况下,ClassWriter.COMPUTE_FRAMES也可能会出错,比如说,有些代码经过混淆(obfuscate)处理,它里面的stack map frame会变更非常复杂,使用ClassWriter.COMPUTE_FRAMES就会出现错误的情况。针对这种少数的情况,我们可以在不改变原有stack map frame的情况下,使用ClassWriter.COMPUTE_MAXS,让ASM只帮助我们计算max stacks和max locals。
ClassWriter代码示例
生成目标接口
生成目标接口:MyHelloWord
package sample;
public interface MyHelloWord {
}
编码实现:
public class HelloWorldDump implements Opcodes {
public static void main(String[] args) throws Exception {
byte[] bytes = dump();
FileUtils.writeByteArrayToFile(new File("sample/MyHelloWord.class"), bytes);
}
public static byte[] dump() throws Exception {
// (1) 创建ClassWriter对象
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// (2) 调用visitXxx()方法
cw.visit(
V1_8, // version
ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, // access
"sample/MyHelloWord", // name
null, // signature
"java/lang/Object", // superName
null // interfaces
);
cw.visitEnd();
// (3) 调用toByteArray()方法
return cw.toByteArray();
}
}
运行结果:

在上述代码中,我们调用了visit()方法、visitEnd()方法和toByteArray()方法。
由于sample.HelloWorld这个接口中,并没有定义任何的字段和方法,因此,在上述代码中没有调用visitField()方法和visitMethod()方法。
visit()方法
在这里,我们重点介绍一下visit(version, access, name, signature, superName, interfaces)方法的各个参数:
version: 表示当前类的版本信息。在上述示例代码中,其取值为Opcodes.V1_8,表示使用Java 8版本。access: 表示当前类的访问标识(access flag)信息。在上面的示例中,access的取值是ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,也可以写成ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE。如果想进一步了解这些标识的含义,可以参考Java Virtual Machine Specification的Chapter 4. The class File Format部分。name: 表示当前类的名字,它采用的格式是Internal Name的形式。signature: 表示当前类的泛型信息。因为在这个接口当中不包含任何的泛型信息,因此它的值为null。superName: 表示当前类的父类信息,它采用的格式是Internal Name的形式。interfaces: 表示当前类实现了哪些接口信息。
Internal Name:
同时,我们也要介绍一下Internal Name的概念:在.java文件中,我们使用Java语言来编写代码,使用类名的形式是Fully Qualified Class Name,例如java.lang.String;将.java文件编译之后,就会生成.class文件;在.class文件中,类名的形式会发生变化,称之为Internal Name,例如java/lang/String。因此,将Fully Qualified Class Name转换成Internal Name的方式就是,将.字符转换成/字符。
| Java Language | Java ClassFile | |
|---|---|---|
| 文件格式 | .java |
.class |
| 类名 | Fully Qualified Class Name | Internal Name |
| 类名示例 | java.lang.String |
java/lang/String |
生成接口+字段+方法
我们的预期目标:生成MyHelloWorld接口。
public interface MyHelloWorld extends Cloneable {
int LESS = -1;
int EQUAL = 0;
int GREATER = 1;
int compareTo(Object o);
}
编码实现:
public class HelloWorldGenerateCore implements Opcodes {
public static void main(String[] args) throws Exception {
// (1) 生成byte[]内容
byte[] bytes = dump();
FileUtils.writeByteArrayToFile(new File("sample/MyHelloWord.class"), bytes);
}
public static byte[] dump() throws Exception {
// (1) 创建ClassWriter对象
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// (2) 调用visitXxx()方法
cw.visit(V1_8, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, "sample/MyHelloWord",
null, "java/lang/Object", new String[]{"java/lang/Cloneable"});
{
FieldVisitor fv1 = cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "I", null, -1);
fv1.visitEnd();
}
{
FieldVisitor fv2 = cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "EQUAL", "I", null, 0);
fv2.visitEnd();
}
{
FieldVisitor fv3 = cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "GREATER", "I", null, 1);
fv3.visitEnd();
}
{
MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "compareTo", "(Ljava/lang/Object;)I", null, null);
mv1.visitEnd();
}
cw.visitEnd();
// (3) 调用toByteArray()方法
return cw.toByteArray();
}
}
运行效果:

visitField()和visitMethod()方法
在这里,我们重点说一下visitField()方法和visitMethod()方法的各个参数:
visitField (access, name, descriptor, signature, value)visitMethod(access, name, descriptor, signature, exceptions)
这两个方法的前4个参数是相同的,不同的地方只在于第5个参数。
access参数:表示当前字段或方法带有的访问标识(access flag)信息,例如ACC_PUBLIC、ACC_STATIC和ACC_FINAL等。name参数:表示当前字段或方法的名字。descriptor参数:表示当前字段或方法的描述符。这些描述符,与我们平时使用的Java类型是有区别的。signature参数:表示当前字段或方法是否带有泛型信息。换句话说,如果不带有泛型信息,提供一个null就可以了;如果带有泛型信息,就需要给它提供某一个具体的值。value参数:是visitField()方法的第5个参数。这个参数的取值,与当前字段是否为常量有关系。如果当前字段是一个常量,就需要给value参数提供某一个具体的值;如果当前字段不是常量,那么使用null就可以了。exceptions参数:是visitMethod()方法的第5个参数。这个参数的取值,与当前方法头(Method Header)中是否具有throws XxxException相关。
我们可以使用PrintASMCodeCore类来查看下面的sample.HelloWorld类的ASM代码,从而观察value参数和exceptions参数的取值:
我们可以使用PrintASMCodeCore类来查看下面的sample.HelloWorld类的ASM代码,从而观察value参数和exceptions参数的取值:
import java.io.FileNotFoundException;
import java.io.IOException;
public class HelloWorld {
// 这是一个常量字段,使用static、final关键字修饰
public static final int constant_field = 10;
// 这是一个非常量字段
public int non_constant_field;
public void test() throws FileNotFoundException, IOException {
// do nothing
}
}
对于上面的代码,
constant_field字段:对应于visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "constant_field", "I", null, new Integer(10))non_constant_field字段:对应于visitField(ACC_PUBLIC, "non_constant_field", "I", null, null)test()方法:对应于visitMethod(ACC_PUBLIC, "test", "()V", null, new String[] { "java/io/FileNotFoundException", "java/io/IOException" })
描述符(descriptor)
在ClassFile当中,描述符(descriptor)是对“类型”的简单化描述。
- 对于字段(field)来说,描述符(descriptor)就是对字段本身的类型进行简单化描述。
- 对于方法(method)来说,描述符(descriptor)就是对方法的接收参数的类型和返回值的类型进行简单化描述。
| Java类型 | ClassFile描述符 |
|---|---|
boolean |
Z(Z表示Zero,零表示false,非零表示true) |
byte |
B |
char |
C |
double |
D |
float |
F |
int |
I |
long |
J |
short |
S |
void |
V |
non-array reference |
L |
array reference |
[ |
对字段描述符的举例:
boolean flag:Zbyte byteValue:Bint intValue:Ifloat floatValue:Fdouble doubleValue:DString strValue:Ljava/lang/String;Object objValue:Ljava/lang/Object;byte[] bytes:[BString[] array:[Ljava/lang/String;Object[][] twoDimArray:[[Ljava/lang/Object;
对方法描述符的举例:
int add(int a, int b):(II)Ivoid test(int a, int b):(II)Vboolean compare(Object obj):(Ljava/lang/Object;)Zvoid main(String[] args):([Ljava/lang/String;)V
生成类
我们的预期目标:生成HelloWorld类。
public class MyHelloWorld {
}
编码实现:
public class HelloWorldGenerateCoreClass implements Opcodes {
public static void main(String[] args) throws Exception {
// (1) 生成byte[]内容
byte[] bytes = dump();
FileUtils.writeByteArrayToFile(new File("sample/MyHelloWord.class"), bytes);
}
public static byte[] dump() throws Exception {
// (1) 创建ClassWriter对象
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// (2) 调用visitXxx()方法
cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/MyHelloWord",
null, "java/lang/Object", null);
//添加无参构造方法
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
cw.visitEnd();
// (3) 调用toByteArray()方法
return cw.toByteArray();
}
}
运行结果:

()和()方法
对于一个类(Class)来说,如果没有提供任何构造方法,Java编译器会自动生成一个默认构造方法。在所有的.class文件中,构造方法的名字是。
另外,如果在.class文件中包含静态代码块,那么就会有一个方法。
package sample;
public class HelloWorld {
static {
System.out.println("static code block");
}
}
上面的静态代码码,对应于visitMethod(ACC_STATIC, "的调用。
总结
- 第一点,我们需要注意
ClassWriter/ClassVisitor中visit()、visitField()、visitMethod()和visitEnd()方法的调用顺序。 - 第二点,我们对于
visit()方法、visitField()方法和visitMethod()方法接收的参数进行了介绍。虽然我们并没有特别介绍visitEnd()方法和toByteArray()方法,并不表示这两个方法不重要,只是因为这两个方法不接收任何参数。 - 第三点,我们介绍了Internal Name和Descriptor(描述符)这两个概念,在使用时候需要加以注意,因为它们与我们在使用Java语言编写代码时是不一样的。
- 第四点,在
.class文件中,构造方法的名字是,表示instance initialization method;静态代码块的名字是() ,表示class initialization method。()