exampleA:
打印結果是3 ,並且靜態代碼塊不會執行,原因是x是常量,在編譯期就會放到MyTest8的常量池當中,然後FinalTest和MyTest8就沒有任何關係了,可以通過反編譯的結果看到。
把x的final去掉之後,靜態代碼塊會打印,再去看反編譯結果:
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變成
3: getstatic #3 // Field com/twodragonlake/jvm/classloader/FinalTest.x:I
意味着MyTest8需要引用到FinalTest,兩者之間存在關係的。
exampleB:
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);
}
}
打印結果:
Parent static block
Child static block
4
即先加載運行類,然後是Parent和Child
我們可以在jvm啓動參數裏邊加入-XX:TraceClassLoading 看到加載順序:
exampleC:
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);
}
}
打印結果:
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:
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的初始化。
}
}
打印結果:
Parent3 static block
3
doSomething...
結論:
主動使用發生在靜態變量定義在哪個類裏邊,而不是是誰調用了變量,定義變量的類會觸發初始化。
exampleE:
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);
}
}
打印結果:
class com.twodragonlake.jvm.classloader.CL
-------------------------
Class CL
class com.twodragonlake.jvm.classloader.CL
調用classLoader類的loadClass方法加載一個類,並不是對類的主動使用,不會導致類的初始化
這個例子驗證了類的初始化時機的反射時機,具體參考之前的文章:主動使用(七種)