前言
在Java中,一個對像在可以被使用之前必須要被正確地初始化,這一點是Java規範規定的。最近我發現了一個有趣的問題,這個問題的答案乍一看下騙過了我的眼睛。看一下這三個類:
package com.ds.test;public class Upper { String upperString; public Upper() { Initializer.initialize(this); }} package com.ds.test;public class Lower extends Upper { String lowerString = null; public Lower() { super(); System.out.println("Upper: " + upperString); System.out.println("Lower: " + lowerString); } public static void main(final String[] args) { new Lower(); }} package com.ds.test;public class Initializer { static void initialize(final Upper anUpper) { if (anUpper instanceof Lower) { Lower lower = (Lower) anUpper; lower.lowerString = "lowerInited"; } anUpper.upperString = "upperInited"; }}運行Lower這個類可以得到什麼輸出?在這個極簡的例子中可以更容易地看到整個形勢,但是這個情形發生在現實中會有非常多的代碼分散一個人的注意力。
不管怎麼樣,輸出是像這樣的:
Upper: upperInitedLower: null;
雖然小示例中使用了String類型, Initializer類的實際代碼中有一個用於註冊的委託對象,與Lower類的功能是相同的― 至少Lower類是這個意圖。但由於某些原因在運行應用程序時沒有工作。取而代之的是,使用了默認路徑,委託對像沒有被設置(null)。
現在稍微改變一下Lower的代碼:
package com.ds.test;public class Lower extends Upper { String lowerString; public Lower() { super(); System.out.println("Upper: " + upperString); System.out.println("Lower: " + lowerString); } public static void main(final String[] args) { new Lower(); }}現在的輸出是這樣的:
Upper: upperInitedLower: lowerInited
發現代碼中的區別了嗎?
是的,這個lowerString字段不再明確地設置為空。為什麼這麼做會有不同。不管怎樣參考類型字段(例如這裡的String )的默認值不是為空的嗎?當然是空的。事實證明,雖然這種微小的變化顯然不會以任何方式改變代碼行為,但是卻讓結果變的不同。
那麼,到底發生了什麼?當查看初始化順序的時候一切就變的清晰了:
1. main()函數調用了Lower構造器。
2. Lower的一個實例被準備好了。意味著所有的字段都被創建並且填充了默認值,例如,引用類型的默認值為空,布爾類型的默認值為false 。在這個時候,任何的對字段的內聯賦值都沒有發生。
3.父類構造器被調用了。這是被語言的特性所強制執行的。所以在其他任何事發生之前,Upper 的構造器被調用了。
4.Upper 這個構造器運行並且指定了一個引用,指向Initializer.initialize()方法新創建的的實例。
5. Initializer類為兩個字段( upperString和lowerString )附上新字符串。通過使用有點骯髒的instanceof實例檢查做到為那兩個字段賦值這不是一個特別好的設計模式,但是也有可行的,不用管那麼多。一旦發生了, upperString和lowerString的引用都不再為空。
6. Initializer.initialize()的調用完成, Upper構造器也同樣完成。
7.現在變得有趣了: Lower實例的構造在繼續。假設在lowerString字段的聲明中沒有明確地=null賦值, Lower構造器恢復執行並且打印出兩個連接到字段的字符串。
然而,如果有一個明確地賦值null 的操作,執行流程會略有不同:當父類構造器完成後,在其餘的構造器運行前,任何變量初始化都會執行(參見java語言規範12.5節)。在這種情況下,之前賦值給lowerString的字符串引用不會再一次被賦予null 。然後繼續執行其餘的函數構造,現在打印lowerString的值為: null 。
這是一個很好的例子,不僅方便我們如何注意一些創建對象的細節(或者知道去哪裡查看Java 編碼規範,打印的或者在線的),還顯示了為什麼像這樣寫初始化是很糟糕的。我們一點都不應該關心Upper 的子類。相反的,如果因為一些原因對某些字段的初始化不能在子類本身被完成,它將只需要它自己的某些初始化幫助類的變體。在這種情況下,如果你使用String lowString或者String lowerString = null是真的沒有任何區別的,它應該是什麼就會是什麼。
總結
以上就是這篇文章的全部內容了,希望這篇文章的內容對大家的學習或者工作能帶來一定的幫助,如果有問題大家可以留言交流。