exampleA:
打印结果是3 ,并且静态代码块不会执行,原因是x是常量,在编译期就会放到MyTest8的常量池当中,然后FinalTest和MyTest8就没有任何关系了,可以通过反编译的结果看到。
把x的final去掉之后,静态代码块会打印,再去看反编译结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| javap -c com.twodragonlake.jvm.classloader.MyTest8 Compiled from "MyTest8.java" public class com.twodragonlake.jvm.classloader.MyTest8 { public com.twodragonlake.jvm.classloader.MyTest8(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return
public static void main(java.lang.String[]); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: getstatic #3 // Field com/twodragonlake/jvm/classloader/FinalTest.x:I 6: invokevirtual #4 // Method java/io/PrintStream.println:(I)V 9: return }
|
可以看到main方法的iconst_3变成
1
| 3: getstatic #3 // Field com/twodragonlake/jvm/classloader/FinalTest.x:I
|
意味着MyTest8需要引用到FinalTest,两者之间存在关系的。
exampleB:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Parent{ static int x = 3; static{ System.out.println("Parent static block"); } }
class Child extends Parent{ static int b = 4; static { System.out.println("Child static block"); } }
public class MyTest9 { static { System.out.println("MyTest9 static block"); } public static void main(String[] args) { System.out.println(Child.b); } }
|
打印结果:
1 2 3
| Parent static block Child static block 4
|
即先加载运行类,然后是Parent和Child
我们可以在jvm启动参数里边加入-XX:TraceClassLoading 看到加载顺序:
exampleC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class Parent2{ static int a = 3; 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 parent; System.out.println("-----------------"); parent = new Parent2(); System.out.println("------------------"); System.out.println(parent.a); System.out.println("------------------"); System.out.println(Child2.b);
} }
|
打印结果:
1 2 3 4 5 6 7 8
| MyTest10 static block ----------------- Parent2 static block ------------------ 3 ------------------ Child2 static block 4
|
解析:
运行main方法之前MyTest10初始化,MyTest10的静态代码块执行
Parent2 parent;//此行代码不会发生任何作用。
System.out.println(“—————–”);
parent = new Parent2();//Parent2主动使用,触发Parent2的初始化,静态代码块被执行,打印【Parent2 static block】
System.out.println(“——————“);
System.out.println(parent.a);//打印Parent2的静态变量
System.out.println(“——————“);
System.out.println(Child2.b);//调用Child2的静态变量 触发Child2的初始化,从而执行Child2 的静态代码块,先打印Child2 static block,在打印4
exampleD:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Parent3{ static int a = 3; static { System.out.println("Parent3 static block"); }
static void doSomething(){ System.out.println("doSomething..."); } }
class Child3 extends Parent3 { static { System.out.println("Child3 static block"); } }
public class MyTest11 { public static void main(String[] args) { System.out.println(Child3.a); //a属于父类,属于对父类Parent3的主动使用, //虽然名字是Child3但是却不是对Child3的主动使用,导致Parent3的初始化,然后Parent3的静态代码块被执行 Child3.doSomething();//调用父类Parent3的静态方法,是对服了的主动使用,触发父类Parent3的初始化。 } }
|
打印结果:
1 2 3
| Parent3 static block 3 doSomething...
|
结论:
主动使用发生在静态变量定义在哪个类里边,而不是是谁调用了变量,定义变量的类会触发初始化。
exampleE:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class CL{ static { System.out.println("Class CL"); } }
public class MyTest12 { public static void main(String[] args) throws Exception { ClassLoader classLoader = ClassLoader.getSystemClassLoader(); Class<?> cl = classLoader.loadClass("com.twodragonlake.jvm.classloader.CL");////不会触发类的初始化 System.out.println(cl); System.out.println("-------------------------"); cl = Class.forName("com.twodragonlake.jvm.classloader.CL");////使用了反射,这属于类初始化时机的反射时机。会触发类的初始化。 System.out.println(cl); } }
|
打印结果:
1 2 3 4
| class com.twodragonlake.jvm.classloader.CL ------------------------- Class CL class com.twodragonlake.jvm.classloader.CL
|
调用classLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化
这个例子验证了类的初始化时机的反射时机,具体参考之前的文章:主动使用(七种)