摘要:對(duì)子類來(lái)講,父類的所有東西都是準(zhǔn)備好的,所以父類部分的初始化要先于子類。父類的初始化順序,遞歸地受以上規(guī)則的控制。例如陳皓的一篇構(gòu)造時(shí)成員初始化的陷阱就談到了一個(gè)讓人頭痛的情況。
需要考慮的”初始化“或者說(shuō)”調(diào)用“部分就4個(gè):
member
method
特殊的method:constructor。
特殊的member:static member/block。
理解記憶方式:
對(duì)多帶帶一個(gè)method來(lái)講,它所在的類已經(jīng)被構(gòu)建了,所以它所在類的constructor一定是已經(jīng)被調(diào)用了。(甚至可以展開說(shuō),method的被調(diào)用順序是最靠后的,因?yàn)闉榱藢?shí)現(xiàn)多態(tài),它必須要late binding)
對(duì)constructor來(lái)講,所有的member應(yīng)該都是可用的,所以member的初始化要先于constructor。(成員變量可以看作是這個(gè)類的固有屬性。要構(gòu)建一個(gè)類,它的固有屬性肯定是要事先準(zhǔn)備好。就好比是,你要new一個(gè)desk類,那至少,這個(gè)desk的width, height這些固有屬性要準(zhǔn)備好吧,否則,我怎么知道應(yīng)該建造一個(gè)多大的desk呢?)
對(duì)member來(lái)講,和所在類息息相關(guān)的static部分,應(yīng)該在定義類的階段,最先就被初始化,所以它會(huì)優(yōu)先于其它的member。
【另一方面,要保證所有member準(zhǔn)備好,對(duì)于子類來(lái)講,可以調(diào)用的父類的member也是應(yīng)該被”正確“準(zhǔn)備好的,而這一條只能由父類的constructor保證。所以父類的constructor要先于子類的member被調(diào)用。】對(duì)子類來(lái)講,父類的所有東西都是準(zhǔn)備好的,所以父類部分的初始化要先于子類。所以,父類的constructor要先于子類的member,以保證子類的member調(diào)用的父類public部分都被事先構(gòu)建好了。(但父類的constructor會(huì)晚于子類的static,這是一個(gè)例外,見下一條)
由于static是和類一起同生共死,所以,即便是子類,在看到這個(gè)名字時(shí)對(duì)應(yīng)的static就已經(jīng)被初始化了,所以它”甚至“優(yōu)先于父類的constructor,但會(huì)晚于父類的static(子類的static可能會(huì)調(diào)用父類的static,所以必須保證父類的static先被初始化)。
父類的初始化順序,遞歸地受以上規(guī)則的控制。
坑那是不是說(shuō)掌握了這些原則就萬(wàn)事大吉了呢?我想并不是,更重要的是,你需要按照規(guī)范去編寫代碼。
但是,你并不總是可以遇到很規(guī)范的代碼,往往會(huì)有奇怪的邏輯和用法。例如陳皓的一篇blog《JAVA構(gòu)造時(shí)成員初始化的陷阱》就談到了一個(gè)讓人頭痛的情況。我們做簡(jiǎn)要地考察:
public class Base { Base() { preProcess(); } void preProcess() {} } public class Derived extends Base { public String whenAmISet = "set when declared"; @Override void preProcess() { whenAmISet = "set in preProcess()"; } } public class Main { public static void main(String[] args) { Derived d = new Derived(); System.out.println( d.whenAmISet ); } }
問(wèn)的是,在main()函數(shù)中,whenAmISet的值應(yīng)該是什么。
有了我們上面的分析做基礎(chǔ),我覺(jué)得你不應(yīng)該直接去回答這個(gè)問(wèn)題,而是應(yīng)該反問(wèn),這段代碼的邏輯是否有問(wèn)題。
因?yàn)樵?b>Base這個(gè)類中,構(gòu)造函數(shù)竟然調(diào)用了一個(gè)method來(lái)做初始化,而且,這個(gè)method還在子類中被重寫。
通過(guò)我們上面的邏輯,method的初始化應(yīng)該放在最后,因?yàn)橐С謑ate binding。就算要做初始化,也該把初始化的method設(shè)定為靜態(tài)??蛇@里卻反常地使用了一般的method。所以不得不說(shuō),這樣編寫代碼的邏輯是否有問(wèn)題。
再來(lái)就可以引述陳皓文中總結(jié)性的話了:
在語(yǔ)言設(shè)計(jì)的時(shí)候,“在構(gòu)造函數(shù)中調(diào)用虛函數(shù)”是個(gè)兩難的問(wèn)題。
如果調(diào)用的是父類的函數(shù)的話,這個(gè)有點(diǎn)違反虛函數(shù)的定義。
如果調(diào)用的是子類的函數(shù)的話,這可能產(chǎn)生問(wèn)題的:因?yàn)樵跇?gòu)造子類對(duì)象的時(shí)候,首先調(diào)用父類的構(gòu)造函數(shù),而這時(shí)候如果去調(diào)用子類的函數(shù),由于子類還沒(méi)有構(gòu)造完成,子類的成員尚未初始化,這么做顯然是不安全的。
C++選擇了第一種,而Java選擇了第二種。
無(wú)論是哪一種,這種用法本身其實(shí)就是有問(wèn)題的。我想,除了應(yīng)付面試、考試,這樣的編寫方式就不應(yīng)該讓它存在。而不是反過(guò)來(lái),去鉆研它為什么是對(duì)的。這只不過(guò)是拿更多的錯(cuò)誤去掩蓋已成事實(shí)的錯(cuò)誤。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/77095.html
摘要:對(duì)子類成員數(shù)據(jù)按照它們聲明的順序初始化,執(zhí)行子類構(gòu)造函數(shù)的其余部分。參考類的初始化順序引了大半類加載的時(shí)機(jī) jvm系列 垃圾回收基礎(chǔ) JVM的編譯策略 GC的三大基礎(chǔ)算法 GC的三大高級(jí)算法 GC策略的評(píng)價(jià)指標(biāo) JVM信息查看 GC通用日志解讀 jvm的card table數(shù)據(jù)結(jié)構(gòu) Java類初始化順序 Java對(duì)象結(jié)構(gòu)及大小計(jì)算 Java的類加載機(jī)制 Java對(duì)象分配簡(jiǎn)要流程 年老...
摘要:中的繼承初始化順序父類和子類年齡動(dòng)物可以吃東西類執(zhí)行了年齡狗可以吃東西類執(zhí)行了對(duì)象的屬性和構(gòu)造方法年齡動(dòng)物可以吃東西類執(zhí)行了的 java中的繼承初始化順序 showImg(https://segmentfault.com/img/bVbnBI1?w=1277&h=671); showImg(https://segmentfault.com/img/bVbnBKG?w=811&h=427...
摘要:底層基于拉鏈?zhǔn)降纳⒘薪Y(jié)構(gòu),并在中引入紅黑樹優(yōu)化過(guò)長(zhǎng)鏈表的問(wèn)題。在其之上,通過(guò)維護(hù)一條雙向鏈表,實(shí)現(xiàn)了散列數(shù)據(jù)結(jié)構(gòu)的有序遍歷。 原文地址 LinkedHashMap LinkedHashMap繼承自HashMap實(shí)現(xiàn)了Map接口?;緦?shí)現(xiàn)同HashMap一樣,不同之處在于LinkedHashMap保證了迭代的有序性。其內(nèi)部維護(hù)了一個(gè)雙向鏈表,解決了 HashMap不能隨時(shí)保持遍歷順序和插...
摘要:在面向?qū)ο蟮某绦蛟O(shè)計(jì)語(yǔ)言中,多態(tài)是繼數(shù)據(jù)抽象和繼承之后的第三種基本特征。 在面向?qū)ο蟮某绦蛟O(shè)計(jì)語(yǔ)言中,多態(tài)是繼數(shù)據(jù)抽象和繼承之后的第三種基本特征。 1.再論向上轉(zhuǎn)型 多態(tài)作用:消除類型之間的耦合關(guān)系. 2.轉(zhuǎn)機(jī) 綁定:將一個(gè)方法調(diào)用同一個(gè)方法主體關(guān)聯(lián)起來(lái). 前期綁定:在程序執(zhí)行前就進(jìn)行綁定(面向過(guò)程語(yǔ)言默認(rèn)綁定方式). 后期綁定:也叫動(dòng)態(tài)綁定或運(yùn)行時(shí)綁定,在運(yùn)行時(shí)根據(jù)對(duì)象的類型進(jìn)行綁...
摘要:也就是說(shuō),一個(gè)實(shí)例變量,在的對(duì)象初始化過(guò)程中,最多可以被初始化次。當(dāng)所有必要的類都已經(jīng)裝載結(jié)束,開始執(zhí)行方法體,并用創(chuàng)建對(duì)象。對(duì)子類成員數(shù)據(jù)按照它們聲明的順序初始化,執(zhí)行子類構(gòu)造函數(shù)的其余部分。 類的拷貝和構(gòu)造 C++是默認(rèn)具有拷貝語(yǔ)義的,對(duì)于沒(méi)有拷貝運(yùn)算符和拷貝構(gòu)造函數(shù)的類,可以直接進(jìn)行二進(jìn)制拷貝,但是Java并不天生支持深拷貝,它的拷貝只是拷貝在堆上的地址,不同的變量引用的是堆上的...
摘要:初始化順序有無(wú)父類有將父類加載進(jìn)內(nèi)存。如果有初始化子類對(duì)象。初始化完畢之后,再執(zhí)行構(gòu)造器內(nèi)的方法。初始化完父類后,依次初始化子類的。 初始化順序:1.有無(wú)父類?有:將父類加載進(jìn)內(nèi)存。直到將所有的父類加載完畢。再?gòu)捻攲痈割惏凑沾a的順序執(zhí)行靜態(tài)代碼,執(zhí)行完最頂層的,在執(zhí)行下一層的,依次類推,直到執(zhí)行完所有的靜態(tài)代碼。(1)如果有初始化子類對(duì)象。那么同樣會(huì)先調(diào)用父類的構(gòu)造器,并且會(huì)先執(zhí)行最...
閱讀 2570·2021-11-23 09:51
閱讀 3120·2019-08-30 15:54
閱讀 1070·2019-08-30 14:14
閱讀 3542·2019-08-30 13:59
閱讀 1393·2019-08-29 17:09
閱讀 1468·2019-08-29 16:24
閱讀 2848·2019-08-29 15:43
閱讀 911·2019-08-29 12:45