本文简述Java初始化过程的一些知识点,属于个人学习总结,能力有限,还望各位大神多多指点。
这里有一组类用于展示类的初始化过程。
这个类没有用到,所以实际上不会被加载。
package com.learn_java_with_samples;
public class Test1 {
public Test1() {
System.out.println("constructor in Test1");
}
private static String s1 = "static field 1 in Test1";
static {
System.out.println("static block 1 in Test1");
}
{
System.out.println("non static block 1 in Test1");
}
private static String s2 = "static field 2 in Test1";
static {
System.out.println("static block 2 in Test1");
}
{
System.out.println("non static block 2 in Test1");
}
}
这个类是一个父类,没有被直接引用,但是会被加载并初始化。
package com.learn_java_with_samples;
public class Test2 {
public Test2() {
System.out.println("constructor in Test2");
}
private static String s1 = "static field 1 in Test2";
static {
System.out.println("static block 1 in Test2");
}
{
System.out.println("non static block 1 in Test2");
}
private static String s2 = "static field 2 in Test2";
static {
System.out.println("static block 2 in Test2");
}
{
System.out.println("non static block 2 in Test2");
}
public static void test() {
System.out.println("static method in Test2");
}
}
这个类被显示初始化了两次。
package com.learn_java_with_samples;
public class Test3 extends Test2 {
public Test3() {
System.out.println("constructor in Test3");
}
private static String s1 = "static field 1 in Test3";
static {
System.out.println("static block 1 in Test3");
}
{
System.out.println("non static block 1 in Test3");
}
private static String s2 = "static field 2 in Test3";
static {
System.out.println("static block 2 in Test3");
}
{
System.out.println("non static block 2 in Test3");
}
public static void test() {
System.out.println("static method in Test3");
}
}
测试类入口。运行这个类时要加上JVM参数 -verbose 以便打印更详细信息。
package com.learn_java_with_samples;
public class Test {
public static void main(String[] args) {
System.out.println("test start");
Test3 t1 = new Test3();
Test3 t2 = new Test3();
System.out.println("test end");
}
}
后半段输出节选。
......
[0.076s][info][class,load] sun.security.util.Debug source: jrt:/java.base
[0.077s][info][class,load] com.learn_java_with_samples.Test source: file:/Users/zhangxin/Documents/workspace/learn_java_with_samples/bin/
[0.077s][info][class,load] java.lang.PublicMethods$MethodList source: jrt:/java.base
[0.077s][info][class,load] java.lang.PublicMethods$Key source: jrt:/java.base
[0.077s][info][class,load] java.lang.Void source: jrt:/java.base
test start
[0.078s][info][class,load] com.learn_java_with_samples.Test2 source: file:/Users/zhangxin/Documents/workspace/learn_java_with_samples/bin/
[0.078s][info][class,load] com.learn_java_with_samples.Test3 source: file:/Users/zhangxin/Documents/workspace/learn_java_with_samples/bin/
static block 1 in Test2
static block 2 in Test2
static block 1 in Test3
static block 2 in Test3
non static block 1 in Test2
non static block 2 in Test2
constructor in Test2
non static block 1 in Test3
non static block 2 in Test3
constructor in Test3
non static block 1 in Test2
non static block 2 in Test2
constructor in Test2
non static block 1 in Test3
non static block 2 in Test3
constructor in Test3
test end
[0.078s][info][class,load] jdk.internal.misc.TerminatingThreadLocal$1 source: jrt:/java.base
[0.079s][info][class,load] java.lang.Shutdown source: jrt:/java.base
[0.079s][info][class,load] java.lang.Shutdown$Lock source: jrt:/java.base
由此输出可以看出以下顺序:
- Test类被加载,并输出“test start”。
- 加载Test2,因为在实例化Test3的时候发现Test2是其父类。
- 加载Test3。
- 顺序执行父类Test2中的静态块。
- 顺序执行子类Test3中的静态块。
- 顺序执行父类Test2中的非静态块。
- 执行父类Test2中的构造方法。
- 顺序执行子类Test3中的非静态块。
- 执行子类Test3中的构造方法。
- 上述步骤6~9被重新执行一遍,但这次是在实例化t2。
- 执行后续语句。
从以上执行顺序可以看出以下几点:
- Test1虽然也在这个项目中,但没有被用到,所以压根儿没有被加载。
- 在加载子类的时候,会依次寻找其父类加载。
- 在初始化子类的时候,会依次寻找其父类,并从其最顶端父类开始依次初始化。
- 所有静态字段和代码块仅在第一次初始化该类的时候被执行。
- 非静态字段和代码块在每次初始化时都会被执行。
- 静态方法仅在被调用时被执行。
本文所有代码在JDK 11上测试通过。