地基不牢,地动山摇。大家好,我是课代表。公众号关注:Java课代表,获取更多实用干货。第一个问题检查代码执行的顺序:publicclassParent{static{System.out.println("Parentstaticinitialblock");}{System.out.println("父初始块");}publicParent(){System.out.println("父构造函数块");}}publicclassChildextendsParent{static{System.out.println("Childstaticinitialblock");}{System.out.println("子初始块");}privateHobbyhobby=newHobby();publicChild(){System.out.println("子构造函数块");}}publicclassHobby{static{System.out.println("Hobbystaticinitialblock");}publicHobby(){System.out.println("爱好构造块");}}当执行newChild()时,上面的代码会输出什么?相信很多同学都遇到过这样的问题。他们可能已经查过资料,然后忘记了,仍然无法再次回答。接下来课代表将带大家通过4个步骤来拆解这段代码的执行顺序,并以此来总结规律。1、编译器优化了什么?下面两段代码比较了编译前后的变化:}{System.out.println("子初始块");}privateHobbyhobby=newHobby();publicChild(){System.out.println("子构造函数块");}}编译后的Child.classpublicclassChildextendsParent{privateHobbyhobby;publicChild(){System.out.println("子初始块");this.hobby=newHobby();System.out.println("子构造函数块");}static{System.out.println("子静态初始块");}}通过对比可以看出,编译器将初始化块和实例字段赋值操作移到了构造函数代码之前,并保留了相关代码的顺序。事实上,如果有多个构造函数,初始化代码也会被复制和移动过来。基于此,可以得出第一优先顺序:初始化代码>构造函数代码2.static的作用是什么?一个类的加载过程大致可以分为三个阶段:Loading->Linking->Initialization初始化阶段可以由《周志明》P359《触发类初始化的8种情况》中的8种情况触发):当使用newkeywordtoinstantiateanobjectreadorsetastaticfieldofatype(except"constant"))callastaticmethodofstaticmethod当使用反射调用类时当初始化一个类时,如果发现父类没有初始化完成后,先触发其父类初始化虚拟机启动时,会先初始化主类(包含main()方法的类)。MethodHandle实例第一次被调用时,会初始化MethodHandle指向的类。如果在接口中定义了默认方法(默认修饰的接口方法),则初始化接口的实现类,必须先初始化接口。第2项和第3项由静态代码触发。其实初始化阶段就是执行类构造函数的方法的过程。这个方法是编译器自动生成的,它收集了所有被static修饰的类变量的赋值动作和静态语句块(static{}块),并保留了这些代码出现的顺序。根据第5条,JVM会确保子类在执行方法之前,父类的方法已经执行完毕。总结一下:访问类变量或者静态方法都会触发类的初始化,而类的初始化就是执行,也就是执行静态修改赋值动作和static{}块,而JVM保证执行先初始化父类,再初始化子类。由此得出第二优先级顺序:父类的静态代码>子类的静态代码3.静态代码只执行我们都知道,静态代码(静态方法除外)只执行一次。有没有想过这个机制是怎么保障的?答案是:双亲委托模型。JDK8及之前的双亲委托模型是:应用类加载器→扩展类加载器→启动类加载器正常开发写的类默认由应用类加载器加载,会委托给它的父类:扩展类加载器.并且扩展类加载器将委托给它的父类:启动类加载器。只有当父类加载器反馈加载请求无法完成时,子加载器才会尝试自己完成加载。这个过程就是双亲委派。三者之间的父子关系不是通过Inheritance,而是通过组合模式实现的。这个过程的实现也很简单,关键实现代码如下所示:loaded,直接返回classClass>c=findLoadedClass(name);如果(c==null){尝试{如果(父级!=null){c=父级。加载类(名称,假);}else{c=findBootstrapClassOrNull(名称);}}catch(ClassNotFoundExceptione){//如果父类抛出ClassNotFoundException//说明父类无法完成加载请求}if(c==null){//如果父类无法加载,则由子类加载c=findClass(name);}}如果(解决){resolveClass(c);}returnc;}结合注释,相信大家很容易理解。父母委托的代码表明,在同一个类加载器下,一个类只能被加载一次,这就限制了它只能被初始化一次。所以类中的静态代码(静态方法除外)只在类初始化时执行一次所有类变量的赋值动作和静态语句块(static{}块)并保留代码的出现顺序,它类初始化时会执行相应的,编译器也会生成一个方法,该方法会收集实例字段的赋值动作、初始化语句块({}块)和构造函数(Constructor)中的代码,以及代码的出现顺序是保留的,会在new指令之后执行所以,当我们创建一个新的类时,如果JVM没有加载这个类,它会先初始化,然后再实例化。至此,第三个优先级规则呼之欲出:静态代码(静态{}块,静态字段赋值语句)>初始化代码({}块,实例字段赋值语句)5.正则练习结合前面三个规则,总结了下面两个:1.静态代码(静态{}块,静态字段赋值语句)>初始化代码({}块,实例字段赋值语句)>构造函数代码2.父类的静态代码>子类的静态根据前面的代码总结,初始化代码和构造函数代码被编译器收集到中,静态代码被收集到中,所以再次合并上面的规则:父类>subclass>Parentclass>Subclass对应一开始的问题,我们来练习一下:执行newChild()时,new关键字触发Child类的初始化,JVM发现知道它有一个父类,那么首先初始化Parent类,开始执行Parent类的方法,然后执行Child类的方法(还记得收集了什么吗?)。然后开始实例化Child类的一个对象。这时候你要执行Child的方法,发现它有父类,先执行父类的方法,再执行子类的(还记得init>收集任何东西吗?)。相信看到这里,你对文章开头的问题已经有了答案。你不妨手写输出序列,然后自己写代码验证。结束语静态在正常开发中经常使用。每次写,心里总有两个问号。为什么要使用静态?不能用吗?这对应开篇第一句:基础薄弱,地动山摇。从这篇文章可以看出,static的应用远不止是类变量,静态方法那么简单。在经典的单例模式中,你会看到static的各种用法,下篇文章会花式写出单例模式的写法。【推荐阅读】RabbitMQ教程Freemarker教程(一)——模板开发手册下载的附件名总是乱码?是时候阅读RFC文档了!MySQL优先级队列(你肯定会踩到的orderbylimit问题)理解起来并不容易。欢迎点赞、关注、分享。搜索:【Java课代表】,关注公众号,及时获取更多Java干货。