类初始化
字节码角度分析类初始化过程
init 方法
看看空的类的
package com.chi.jvm.clazz;
public class InitTest1 {
}
0 aload_0
1 invokespecial #1 <java/lang/Object.<init> : ()V>
4 return
编译器会自动为类加上一个无参构造器,并调用Object
父类的无参构造方法
package com.chi.jvm.clazz;
public class InitTest1 {
private int a = 1;
}
// 调用父类构造器
0 aload_0
1 invokespecial #1 <java/lang/Object.<init> : ()V>
// 初始化实例成员属性
4 aload_0
5 iconst_1
6 putfield #2 <com/chi/jvm/clazz/InitTest1.a : I>
9 return
成员变量在默认的构造器方法中初始化,但可以注意到调用父类方法的指令始终放在前面
package com.chi.jvm.clazz;
public class InitTest1 {
private int a;
public InitTest1(){
a = 2;
}
}
// 调用父构造
0 aload_0
1 invokespecial #1 <java/lang/Object.<init> : ()V>
// 初始化为 1
4 aload_0
5 iconst_1
6 putfield #2 <com/chi/jvm/clazz/InitTest1.a : I>
// 赋值为 2
9 aload_0
10 iconst_2
11 putfield #2 <com/chi/jvm/clazz/InitTest1.a : I>
14 return
package com.chi.jvm.clazz;
public class InitTest1 {
private int a;
public InitTest1(){
System.out.println(a); // 输出 0
a = 2;
}
}
0 aload_0
1 invokespecial #1 <java/lang/Object.<init> : ()V>
4 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
7 aload_0
8 getfield #3 <com/chi/jvm/clazz/InitTest1.a : I>
11 invokevirtual #4 <java/io/PrintStream.println : (I)V>
14 aload_0
15 iconst_2
16 putfield #3 <com/chi/jvm/clazz/InitTest1.a : I>
19 return
如果实例属性字段声明阶段没有初始化,在字节码层面上不会添加默认的初始值,但是通过sout能够正常打印,说明默认值是硬编码到字节码文件中的。
clinit 方法
package com.chi.jvm.clazz;
public class ClinitTest1 {
private static int a;
}
// 无clint方法
静态字段
package com.chi.jvm.clazz;
public class ClinitTest1 {
private static int a = 1;
}
// clint
0 iconst_1
1 putstatic #2 <com/chi/jvm/clazz/ClinitTest1.a : I>
4 return
静态代码块
package com.chi.jvm.clazz;
public class ClinitTest1 {
private static int a = 1;
static {
a = 2;
}
static {
a = 3;
}
}
// clinit 方法
0 iconst_1
1 putstatic #2 <com/chi/jvm/clazz/ClinitTest1.a : I>
4 iconst_2
5 putstatic #2 <com/chi/jvm/clazz/ClinitTest1.a : I>
8 iconst_3
9 putstatic #2 <com/chi/jvm/clazz/ClinitTest1.a : I>
12 return
package com.chi.jvm.clazz;
public class ClinitTest2 {
static {
System.out.println("1");
}
static {
System.out.println("2");
}
public ClinitTest2(){
System.out.println(3);
}
public static void main(String[] args) {
new ClinitTest2();
new ClinitTest2();
}
}
/*
1
2
3
3
*/
静态属性和静态代码块只会初始化一次
final 关键字修饰的字段
- 修饰实例属性:必须在声明时初始化,或在构造方法中初始化
- 修饰静态属性:必须在声明时初始化,或在static静态代码块中初始化
在构造方法中调用其他构造方法
- 调用自己:this()
- 调用父类:super()
无论调用哪个构造器,必须放在所有语句之前
总结
- 编译器会提供默认的构造方法,用于调用父类构造方法和初始化实例属性
- 编译器会提供
clinit
方法初始化静态属性、按顺序执行静态代码块 - 在构造器中调用其他构造器方法必须放在第一句
- final修饰的属性必须进行初始化