最近在看 Java 虛擬機類加載的知識點,結果讓我發現了本身一個曾經一直糾結,又沒完全弄懂的類加載黑洞,從而引起下面一系列的測試血案。java
相信面試過的大家也會見過相似下面測試的這幾道題。不過,答案你真的理解了麼?話很少說,直接 GKD。惋惜我不是大佬,因此...哈哈哈 GKD 吧!下面就是測試過程種發現的一些疑惑點,趕忙記錄一波...面試
測試開始,先思考下下面代碼輸出什麼:數組
class Singleton { public Singleton() { System.out.println("Singleton new instance"); } static { System.out.println("Singleton static block"); } { System.out.println("Singleton block !!!"); } } public class NewTest { public static void main(String args[]){ Singleton singleton = new Singleton(); } }
輸出結果:架構
Singleton static block Singleton block !!! Singleton new instance
固然,大佬們應該都能知道答案...畢竟,新手入門級的野怪,誰都打得過。這個對我這小菜雞也算還比較容易理解;加載鏈接過程,沒有須要處理的 static。new Singleton() 直接開始類的初始化了,因此輸出直接按照類的初始化順序來就行了dom
沒有父類的狀況:函數
類的靜態屬性
類的靜態代碼塊
類的非靜態屬性
類的非靜態代碼塊
構造方法學習
有父類的狀況:測試
父類的靜態屬性
父類的靜態代碼塊
子類的靜態屬性
子類的靜態代碼塊
父類的非靜態屬性
父類的非靜態代碼塊
父類構造方法
子類非靜態屬性
子類非靜態代碼塊
子類構造方法優化
這裏有個小誤區,是我本身的誤區~~好比下面這個例子:code
class ParentSingleton{ public static int value = 100; public ParentSingleton(){ System.out.println("ParentSingleton new instance"); } static { System.out.println("ParentSingleton static block"); } { System.out.println("ParentSingleton block !!! "); } }
當要初始化上面這個類的時候,會輸出什麼?
若是這時候,咱們只看上面的初始化順序,會以爲這樣輸出,根據順序來嘛~
ParentSingleton static block ParentSingleton block !!! ParentSingleton new instance ???
OMG,錯了,這裏的順序不是說,只要初始化,就要所有按照順序一一執行...不是這樣的。實際上只會輸出:
ParentSingleton static block
若是有建立這個類的實例,好比 new ParentSingleton(),纔會:
ParentSingleton block !!!
ParentSingleton new instance
是的,這裏的誤區,我曾經一度搞錯了...尷尬。那再看這個測試:
class Singleton { private static Singleton singleton = new Singleton(); private Singleton() { System.out.println("Singleton new instance"); } public static void forTest() { } static { System.out.println("Singleton static block"); } { System.out.println("Singleton block !!! "); } } public class TestSingleton { public static void main(String args[]){ Singleton.forTest(); } }
看完資料的我,逐漸膨脹,畢竟100多斤的胖子,我想的輸出應該是:
Singleton static block Singleton block !!! Singleton new instance
而後運行一看,懵逼了,結果是:
Singleton block !!! Singleton new instance Singleton static block
咋回事啊,小老弟,結果亂套了...爲何不是先執行 static 代碼塊先了。認真想了一波,也不知道對不對,只能瘋狂測試這樣子...
通過一番測試,查看資料...最終...我以爲是這樣子的。整個的流程詳解應該是執行的第一步:Singleton.forTest();這時候,對Singleton類進行加載和鏈接,因此首先須要對它進行加載和鏈接操做。在鏈接-準備階段,要講給靜態變量賦予默認初始值,這裏還沒到執行 forTest;初始值是 singleton = null。加載和鏈接完畢以後,再進行初始化工做:
private static Singleton singleton = new Singleton();
因此執行去到了 new Singleton(); 這裏由於 new 會引發 Singleton 的初始化。須要執行 Singleton構造函數裏面的內容。可是又由於非static初始化塊,這裏面的代碼在建立java對象實例時執行,並且在構造器以前!!!!就是這東西...因此輸出應該是:
Singleton block !!! Singleton new instance
而根據類的初始化順序,要執行 static 代碼塊,應該輸出:
Singleton static block
完成初始化後。接下來就到真正調用 forTest 方法了,方法什麼都不作,沒輸出。因此,總的答案就是:
Singleton block !!! Singleton new instance Singleton static block
這裏最大的緣由就是,鏈接加載的時候,要給屬性初始化,而這裏的初始化又恰好是 建立java 實例,須要執行構造,執行構造的前面又必須先執行 {} 大括號非 static 塊。而不是和第一個測試例子那樣,static 屬性不須要初始化,因此....
IG 永不加班,但我須要哇,繼續測試吧...繼續測試驗證:
class Singleton { private static Singleton singleton = new Singleton(); private Singleton() { System.out.println("Singleton new instance"); } public static Singleton getSingleton() { return new Singleton(); } static { System.out.println("Singleton static block"); } { System.out.println("Singleton block !!! "); } } public class TestSingleton { public static void main(String args[]){ Singleton singleton = Singleton.getSingleton(); } }
輸出結果以下所示。emm, 再次根據上面本身的理解,走一遍,應該是:
Singleton block !!! Singleton new instance Singleton static block Singleton block !!! Singleton new instance
這裏後面第二次 new 爲啥不引發第二次 類的初始化?? 由於一個類只能初始化一次啊!new 只是建立實例,再也不初始化了。因此在調用 getSingleton 的時候,只建立實例就行了,而建立實例就是:
Singleton block !!! Singleton new instance
在同一個類加載器下面只能初始化類一次,若是已經初始化了就沒必要要初始化了。爲何只初始化一次呢?類加載的最終結果就是在堆中存有惟一一個Class對象,咱們經過Class對象找到的那個惟一的。噢?運行看一手,丟,對了..還有存在 final 的時候,和存在父類的時候,下面慢慢再測試驗證....繼續測試:
class Singleton extends ParentSingleton { public Singleton() { System.out.println("Singleton new instance"); } static { System.out.println("Singleton static block"); } { System.out.println("Singleton block !!! "); } } class ParentSingleton{ public ParentSingleton(){ System.out.println("ParentSingleton new instance"); } static { System.out.println("ParentSingleton static block"); } { System.out.println("ParentSingleton block !!! "); } } public class TestSingleton { public static void main(String args[]){ Singleton singleton = new Singleton(); } }
輸出結果以下所示。這個,很明瞭,仍是按照上面的類的初始化,有父類的狀況按順序調用,輸出以下:
ParentSingleton static block Singleton static block ParentSingleton block !!! ParentSingleton new instance Singleton block !!! Singleton new instance
繼續測試以下所示。那我的,又來了...改爲和上面沒有父類同樣的狀況:
class Singleton extends ParentSingleton { private static Singleton singleton = new Singleton(); private Singleton() { System.out.println("Singleton new instance"); } public static Singleton getSingleton() { return singleton; } static { System.out.println("Singleton static block"); } { System.out.println("Singleton block !!! "); } } class ParentSingleton{ public ParentSingleton(){ System.out.println("ParentSingleton new instance"); } static { System.out.println("ParentSingleton static block"); } { System.out.println("ParentSingleton block !!! "); } } public class TestSingleton { public static void main(String args[]){ Singleton singleton = Singleton.getSingleton(); } }
輸出結果以下所示。這裏,就開始懵了...有點。先看結果:
ParentSingleton static block ParentSingleton block !!! ParentSingleton new instance Singleton block !!! Singleton new instance Singleton static block
其實,很容易看清了,如今,再走一遍流程吧!執行到 Singleton.getSingleton() 時,先加載 Singleton ,這時由於 Singleton 有父類,須要須要加載父類先,加載父類 ParentSingleton,根據加載流程,在鏈接-準備階段,要講給靜態變量賦予默認初始值,但父類沒有 static 屬性須要賦值初始化什麼的,可是根據順序,須要初始化static 代碼塊:
ParentSingleton static block
這時候回到子類的加載流程。根據鏈接-準備階段,子類有須要處理的屬性 private static Singleton singleton = new Singleton();賦值默認值先,singleton = null;而後初始化 singleton = new Singleton();根據上面的經驗,這裏是建立實例 ,並引發初始化,正常應該是:
Singleton block !!! Singleton new instance Singleton static block
可是,重點來了 !! 類實例建立過程:按照父子繼承關係進行初始化,首先執行父類的初始化塊部分。而後是父類的構造方法;再執行本類繼承的子類的初始化塊,最後是子類的構造方法,也就是:
ParentSingleton block !!! ParentSingleton new instance
同時子類的初始化,由於初始化子類它有父類,因此須要先初始化父類(可是這裏由於父類已經初始化了,就再也不初始化了)。因此結果是:
ParentSingleton static block ParentSingleton block !!! ParentSingleton new instance Singleton block !!! Singleton new instance Singleton static block
最終測試以下所示:
class Singleton extends ParentSingleton { private static Singleton singleton = new Singleton(); private Singleton() { System.out.println("Singleton new instance"); } public static Singleton getSingleton() { return singleton; } static { System.out.println("Singleton static block"); } { System.out.println("Singleton block !!! "); } } class ParentSingleton{ private static ParentSingleton parentSingleton = new ParentSingleton(); public ParentSingleton(){ System.out.println("ParentSingleton new instance"); } static { System.out.println("ParentSingleton static block"); } { System.out.println("ParentSingleton block !!! "); } } public class TestSingleton { public static void main(String args[]){ Singleton singleton = Singleton.getSingleton(); } }
測試結果以下所示:
ParentSingleton block !!! ParentSingleton new instance ParentSingleton static block ParentSingleton block !!! ParentSingleton new instance Singleton block !!! Singleton new instance Singleton static block
加載一個類時,先加載父類。按照先加載,建立實例,初始化,這個順序就發現很通順的寫出答案了。哈哈哈哈哈,終於清楚了。因此一切的一切,都是建立實例這個東西。搞得我頭暈。
部分特殊不引發類初始化記錄,先記錄下吧。
經過子類引用父類的靜態字段,不會致使子類初始化,對於靜態字段,只有直接定義這個字段的類纔會被初始化
經過數組定義來引用類,不會觸發此類的初始化
常量在編譯階段會存入調用類的常量池中,本質上並無直接引用到定義常量的類,所以不會觸發定義常量的類的初始化
public static final int x =6/3; 可以在編譯時期肯定的,叫作編譯常量,不會引發類的初始化!!!
public static final int x =new Random().nextInt(100); 運行時才能肯定下來的,叫作運行時常量,運行常量會引發類的初始化!!!
在虛擬機規範中使用了一個很強烈的限定語:「有且僅有」,這5種場景中的行爲稱爲對類進行主動引用。除此以外,全部引用類的方式都不會觸發初始化,稱爲被動引用。
5種必須初始化的場景以下
這4條指令對應的的常見場景分別是:使用new關鍵字實例化對象、讀取或設置一個類的靜態字段(被final修飾、已在編譯期把結果放入常量池的靜態字段除外)的時候,以及調用一個類的靜態方法的時候。
注:靜態內容是跟類關聯的而不是類的對象。
注:反射機制是在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性和方法
對於任意一個對象,都可以調用它的任意一個方法和屬性
這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制,這相對好理解爲何須要初始化類。
注:子類執行構造函數前需先執行父類構造函數
注:main方法是程序的執行入口
注:JDK1.7的一種新增的反射機制,都是對類的一種動態操做
這回,之後看代碼的時候,就不會再被這些執行加載順序弄混了,對優化代碼可能仍是有幫助的吧。
再不說,也能再讓我看到這些測試題,或者問我加載的過程,怎麼也能處理回答個7788了吧。
可能其中我的理解有部分紕漏,還請大佬們指出~~蟹蟹鴨!