最近在读java深度历险这本书,书中第二章有个关于静态块执行时机的讨论,这一改我以前的认知啊。
很多博客或是书籍都认为静态块是类加载的时候执行的,真相真的是这样么?
我们先来看两个例子:
example 1:
public class Test {
public static void main(String[] args) throws Exception {
System.out.println("initialize...");
Class c = Class.forName("TestStatic");
System.out.println("instance...");
Object obj1 = c.newInstance();
Object obj2 = c.newInstance();
System.out.println("end ...");
}
}
public class TestStatic {
static {
System.out.println("*******************");
}
}
输出结果:
这次结果是TestStatic的静态块是在类加载的时候执行的,接下来我们看看另一个例子
example 2:
public class Test {
public static void main(String[] args) throws Exception {
System.out.println("initialize...");
Class c = Class.forName("TestStatic", false, Test.class.getClassLoader());
System.out.println("instance...");
Object obj1 = c.newInstance();
Object obj2 = c.newInstance();
System.out.println("end ...");
}
}
输出结果:
这次我们看到静态块是在实例化的时候执行的,为何会造成这两种截然不同的结果呢?
原因在于两次使用了不同的类加载方法
Class c = Class.forName("TestStatic");
Class c = Class.forName("TestStatic", false, Test.class.getClassLoader());
看看源码可以发现两种类加载方式最终调用的都是一个native方法forName0()
第三个参数ClassLoader都是代码执行者Test类的ClassLoader,由此可以看出两次静态块执行时机的不同就是一个参数initialize值决定的。同时,我们看到即使是后面再实例化了一个obj2,static块也没有再次执行,这表明静态块仅仅在第一次实例化才会执行。
关于参数initialize的含义我看到网上有博客说就是否对加载的类进行初始化,设置为true时会类进行初始化,代表会执行类中的静态代码块,以及对静态变量的赋值等操作。联系类加载的过程相关知识,我们知道给静态变量赋初始值是在“准备”阶段,真正赋上代码中的值“9”在“初始化”阶段(final修饰的静态变量赋值“9”在“准备”阶段),开始我以为设置为false就可以跳过类加载的“初始化”步骤,但测试过后发现这个参数initialize并不可以控制对静态变量的赋值。
测试:
TestStatic类中新增static变量i,赋值9
public class TestStatic {
static int i = 9;
static {
System.out.println("*******************");
}
}
Test类设置initialize为false的加载方式,同时在加载过后以及实例化过后新增打印语句:
public class Test {
public static void main(String[] args) throws Exception {
System.out.println("initialize...");
Class c = Class.forName("TestStatic", false, Test.class.getClassLoader());
System.out.println("1. i = " + TestStatic.i);
System.out.println("instance...");
Object obj = c.newInstance();
System.out.println("2. i = " + TestStatic.i);
System.out.println("end ...");
}
}
然而,结果是加载过后实例化之前静态变量i的赋值动作已经完成,initialize参数为false并没有控制住静态变量的赋值动作。
不知道是不是我测试的方式有问题,如果哪位朋友知道请告知下,感激不尽!
综上,静态块实际是在第一次实例化的时候才会执行,只不过我们在日常写代码时使用的都是initialize为true的类加载方式,所以会产生静态块是类加载时执行的错误认知。
以上如果有理解不对的地方还望大家能够指正和包容,谢谢!
********************************************************************************************************************
已经有大神指出这个测试方式有问题,对于类名.静态变量这种取值方式,JVM会立即对该类做类加载的初始化动作的,所以此时测试出来的“9”并不能推导出initialize参数为false没有控制住静态变量的赋值动作。
这段话摘自《深入理解Java虚拟机第2版》第七章中的类加载的时机的内容:
虚拟机规范规定了,当遇到new、getstatic、putstatic或invokestatic这4条字节码指令是,如果没有进行过初始化则需要先触发初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或者只一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
非常感谢指出错误的朋友,以免误导大家!