类加载器与类初始化深度剖析
之前已经对类初始化相关的东东进行了非常细致的学习,另外也对类加载器进行了初始的了解,其中对于类的主动方式会有七种,这里再来回顾一下:
这次做一些实验来进一步巩固关于类初始化相关的知识点,下面开始:
实验一:
为什么?如果彻底搞清楚了之前关于类初始化的知识点,那这个不难解释,原因是由于:FinalTest.x目前是个编译期常量,它会在编译期间将其放到常量池,并不会导到FinalTest的主动使用,为了更进一步能看清本质,咱们将MyTest8的字节码文件进行反编译,如下:
好下面继续改造:
它是属于七种主动使用类的情况之一,如下:
接着再来修改:
此时继续反编译一下:
实验二:
为什么是按这样一个顺序输出的呢?由于使用到了main()静态方法,所以会导致MyTest9的主动使用,所以"MyTes9 static block"会被初始化,其原因还是七种主动使用的情况之一,如下:
接着由于"Child.b"子类静态变量的使用会导到它的父类进行初始化,所以"Parent static block"输出了,最后自己再初始化,所以"Child static block"输出了,最终再输出要打印的变量的值,为了进一步查看类的加载信息,还是给JVM加上"-XX:+TrancClassLoading"参数来进行观测,如下:
实验三:
package com.jvm.classloader; class Parent2 { static int a = 2; static { System.out.println("Parent2 static block"); } } class Child2 extends Parent2 { static int b = 4; static { System.out.println("Child2 static block"); } } public class MyTest10 { static { System.out.println("MyTest10 static block"); } public static void main(String[] args) { Parent2 parent2; System.out.println("------------------"); parent2 = new Parent2(); System.out.println("------------------"); System.out.println(parent2.a); System.out.println("------------------"); System.out.println(Child2.b); } }
编译运行:
这又是为何呢?分析一下:由于调用了main()方法,则MyTest10会被初始化,所以“MyTest10 static block”被输出了,而这个不会导致任何输出:
接着生成Parent2的实例则会导到它被初始化,如下:
而它对应这种主动使用的情况:
,所以"Parent2 static block"被输出了,接着输出Parent2.a,所以“3”就被输出出来了,最后这句话会导到Child2被初始化,如下:
照理在初始化子类的时候,应该先初始化父类Parent2,但是由于Parent2已经在之前初始化过了,所以这里就不会再次初始了,接着走Child2的初始化逻辑,于是乎“Child2 static block”就被打印出来了,最终再打印Child2.b值"4"。
实验四:
下面分析一下:由于Child3.a调用的是它父类Parent3里面的变量,所以也就是对Parent3的主动使用,于是乎就会打印出:
而接下来还是通过Child3去调用它父类的doSomething(),同样也不会导至Child3的初始化,而只会导致Parent3进行初始化,但是由于它之前已经被初始化了,所以输出为:
所以总结一下:如果用子类去访问父类的静态变量或静态方法,表示的是对于父类的主动使用,而非表示对子类的主动使用。
实验五:
这个实现会涉及到类加载器相关的东东,但是还是说明类初始化相关的知识点,具体如下:
其中可以发现通过系统加载类时是不会导致类的初始化的,而通过Class.forName是会导致类的初始化,其它这也对应七种主动使用的这种情况:
所以总结一下:调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,也就不会导致类的初始化。