Java中對(duì)象初始化順序的詳細(xì)介紹
前言
在Java中,一個(gè)對(duì)象在可以被使用之前必須要被正確地初始化,這一點(diǎn)是Java規(guī)范規(guī)定的。最近我發(fā)現(xiàn)了一個(gè)有趣的問(wèn)題,這個(gè)問(wèn)題的答案乍一看下騙過(guò)了我的眼睛??匆幌逻@三個(gè)類(lèi):
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"; } }
運(yùn)行 Lower
這個(gè)類(lèi)可以得到什么輸出?在這個(gè)極簡(jiǎn)的例子中可以更容易地看到整個(gè)形勢(shì),但是這個(gè)情形發(fā)生在現(xiàn)實(shí)中會(huì)有非常多的代碼分散一個(gè)人的注意力。
不管怎么樣,輸出是像這樣的:
Upper: upperInited Lower: null;
雖然小示例中使用了 String
類(lèi)型,Initializer
類(lèi)的實(shí)際代碼中有一個(gè)用于注冊(cè)的委托對(duì)象,與 Lower
類(lèi)的功能是相同的 — 至少 Lower
類(lèi)是這個(gè)意圖。但由于某些原因在運(yùn)行應(yīng)用程序時(shí)沒(méi)有工作。取而代之的是,使用了默認(rèn)路徑,委托對(duì)象沒(méi)有被設(shè)置 (null)。
現(xiàn)在稍微改變一下 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(); } }
現(xiàn)在的輸出是這樣的:
Upper: upperInited Lower: lowerInited
發(fā)現(xiàn)代碼中的區(qū)別了嗎?
是的,這個(gè) lowerString
字段不再明確地設(shè)置為空。為什么這么做會(huì)有不同。不管怎樣參考類(lèi)型字段(例如這里的 String
)的默認(rèn)值不是為空的嗎?當(dāng)然是空的。事實(shí)證明,雖然這種微小的變化顯然不會(huì)以任何方式改變代碼行為,但是卻讓結(jié)果變的不同。
那么,到底發(fā)生了什么?當(dāng)查看初始化順序的時(shí)候一切就變的清晰了:
1.main()
函數(shù)調(diào)用了 Lower
構(gòu)造器。
2.Lower
的一個(gè)實(shí)例被準(zhǔn)備好了。意味著所有的字段都被創(chuàng)建并且填充了默認(rèn)值,例如,引用類(lèi)型的默認(rèn)值為空,布爾類(lèi)型的默認(rèn)值為 false
。在這個(gè)時(shí)候,任何的對(duì)字段的內(nèi)聯(lián)賦值都沒(méi)有發(fā)生。
3.父類(lèi)構(gòu)造器被調(diào)用了。這是被語(yǔ)言的特性所強(qiáng)制執(zhí)行的。所以在其他任何事發(fā)生之前,Upper 的構(gòu)造器被調(diào)用了。
4.Upper 這個(gè)構(gòu)造器運(yùn)行并且指定了一個(gè)引用,指向 Initializer.initialize()
方法新創(chuàng)建的的實(shí)例。
5.Initializer
類(lèi)為兩個(gè)字段( upperString
和 lowerString
)附上新字符串。通過(guò)使用有點(diǎn)骯臟的 instanceof
實(shí)例檢查做到為那兩個(gè)字段賦值 – 這不是一個(gè)特別好的 設(shè)計(jì)模式 ,但是也有可行的,不用管那么多。一旦發(fā)生了,upperString
和 lowerString
的引用都不再為空。
6.Initializer.initialize()
的調(diào)用完成,Upper
構(gòu)造器也同樣完成。
7.現(xiàn)在變得有趣了:Lower
實(shí)例的構(gòu)造在繼續(xù)。假設(shè)在 lowerString
字段的聲明中沒(méi)有明確地 =null
賦值,Lower
構(gòu)造器恢復(fù)執(zhí)行并且打印出兩個(gè)連接到字段的字符串。
然而,如果有一個(gè)明確地賦值 null 的操作,執(zhí)行流程會(huì)略有不同:當(dāng)父類(lèi)構(gòu)造器完成后,在其余的構(gòu)造器運(yùn)行前,任何變量初始化都會(huì)執(zhí)行(參見(jiàn)java語(yǔ)言規(guī)范12.5節(jié))。在這種情況下,之前賦值給 lowerString
的字符串引用不會(huì)再一次被賦予 null 。然后繼續(xù)執(zhí)行其余的函數(shù)構(gòu)造,現(xiàn)在打印 lowerString
的值為: null 。
這是一個(gè)很好的例子,不僅方便我們?nèi)绾巫⒁庖恍﹦?chuàng)建對(duì)象的細(xì)節(jié)(或者知道去哪里查看 Java 編碼規(guī)范,打印的或者在線的),還顯示了為什么像這樣寫(xiě)初始化是很糟糕的。我們一點(diǎn)都不應(yīng)該關(guān)心 Upper 的子類(lèi)。相反的,如果因?yàn)橐恍┰驅(qū)δ承┳侄蔚某跏蓟荒茉谧宇?lèi)本身被完成,它將只需要它自己的某些初始化幫助類(lèi)的變體。在這種情況下,如果你使用 String lowString
或者 String lowerString = null
是真的沒(méi)有任何區(qū)別的,它應(yīng)該是什么就會(huì)是什么。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望這篇文章的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有問(wèn)題大家可以留言交流。
相關(guān)文章
利用Springboot實(shí)現(xiàn)Jwt認(rèn)證的示例代碼
這篇文章主要介紹了利用Springboot實(shí)現(xiàn)Jwt認(rèn)證的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12maven依賴(lài)版本沒(méi)有按照最短路徑原則生效的解決方案
這篇文章主要介紹了maven依賴(lài)版本沒(méi)有生效的解決方案,幫助大家更好的理解和使用springboot框架,感興趣的朋友可以了解下2021-01-01Java?中很好用的數(shù)據(jù)結(jié)構(gòu)EnumSet
這篇文章主要介紹了Java?中很好用的數(shù)據(jù)結(jié)構(gòu)EnumSet,EnumMap即屬于一個(gè)Map,下文圍繞主題展開(kāi)詳細(xì)內(nèi)容,需要的小伙伴可以參考參考一下2022-05-05Spring Cache相關(guān)知識(shí)總結(jié)
今天帶大家學(xué)習(xí)Spring的相關(guān)知識(shí),文中對(duì)Spring Cache作了非常詳細(xì)的介紹,對(duì)正在學(xué)習(xí)Java Spring的小伙伴們很有幫助,需要的朋友可以參考下2021-05-05Java實(shí)現(xiàn)線性表的鏈?zhǔn)酱鎯?chǔ)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)線性表的鏈?zhǔn)酱鎯?chǔ),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10springcloud整合openfeign使用實(shí)例詳解
這篇文章主要介紹了springcloud整合openfeign使用詳解,以springcloud中提供的遠(yuǎn)程接口調(diào)用組件openfeign為例,來(lái)聊聊openfeign的詳細(xì)使用,需要的朋友可以參考下2023-03-03Java 8 中 Map 騷操作之 merge() 的使用方法
本文簡(jiǎn)單介紹了一下Map.merge()的方法,除此之外,Java 8 中的HashMap實(shí)現(xiàn)方法使用了TreeNode和 紅黑樹(shù),原理很相似,今天通過(guò)本文給大家介紹Java 8 中 Map 騷操作之 merge() 的用法 ,需要的朋友參考下吧2021-07-07Java實(shí)現(xiàn)synchronized鎖同步機(jī)制
synchronized是java內(nèi)置的同步鎖實(shí)現(xiàn),本文就詳細(xì)的介紹一下Java實(shí)現(xiàn)synchronized鎖同步機(jī)制,具有一定的參考價(jià)值,感興趣的可以了解一下2021-11-11