开始我们先来看一段代码
package classLoader; class a { public a() { System.out.println("init a"); } public void say(){ System.out.println("say a"); } { System.out.println("block a"); } static { System.out.println("static a"); } } public class b extends a { public b() { System.out.println("init b"); } { System.out.println("block b"); } public void say(){ System.out.println("say b"); } static { System.out.println("static b"); } public static void main(String[] args) { System.out.println("主程序开始!"); a test = new b(); //进行类的初始化 test.say(); System.out.println("重新创建对象!"); test = new b(); } }
我们可以看出静态代码优先于普通代码快更优先与构造函数。同时静态代码块在对象创建前就运行了静态代码块,总是先运行父类代码再运行子类代码,我们也可以说父类代码先行于子类代码块。而在给test重新赋予一个新的a对象时并没有执行静态代码快。接下来我们来分析出现这些现象的原因。
首先我们先来聊聊类的初始化
java虚拟机对于5种情况必须进行初始化。
- 使用new关键字实例化对象吗,读取或设置一个静态字段,以及调用一个静态方法
- 进行反射调用的时候,如果没有进行初始化先触发其初始化。
- 初始化一个类时,发现其父类没有进行初始化,则需要先进性父类的初始化。
- 虚拟机启动需要执行一个主类时(包含main()方法的类)。
- 使用jdk1.7的动态语言支持时如果一个MethodHandle实例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的句柄时,并且其没有进行初始化则先出发其初始化
在准备阶段进行系统要求的初始值赋值。而在初始化阶段执行类构造器<clinit>()方法的过程。。
<clinit>()所发挥的作用
<clinit>()是初始化阶段主要执行的代码。根据程序员的要求进行变量的赋值和其他资源的调用。<clinit>()方法会自动收集所有类变量赋值和静态代码块。该方法与构造函数不同,他不需要显式的调用父类构造器。在执行子类的<clinit>()方法时父类的<clinit>()已经执行完毕。也就意味着父类的构造器先行于子类的构造器。所以会先输出父类静态代码块的内容,再输出子类静态代码块的内容。
注意:<clinit>()方法不是必须的,如果没有静态代码,块也没有变量的赋值。那么其可以不生成<clinit>()方法
<clinit>()是线程安全的,但如果一个县城的<clinit>()耗时很长会导致多个进程阻塞。也就是说stati代码块应用不当会造成进程阻塞。
Q&A
在给test重新赋予一个新的a对象时并没有执行静态代码快的原因?
该类已经初始化了,不用再进行初始化,故不需要进行初始化,也就不需要执行<clinit>(),自然也就不会输出静态代码的内容。
静态代码块,普通代码块,构造函数运行顺序?
由程序运行结果可知:静态代码块>普通代码块>构造函数
注意;父类代码先行于子类代码,代码进行初始化5种情况的第3种