Java的聲明和初始化:細(xì)看OO程序執(zhí)行的順序
在介紹Java的聲明和初始化的執(zhí)行順序之前,讓我們先來看兩個類:Base和Derived類。注意其中的whenAmISet成員變量,和方法preProcess()
- 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()";
- }
- }
如果我們構(gòu)造一個子類實例,那么,whenAmISet 的值會是什么呢?
- public class Main
- {
- public static void main(String[] args)
- {
- Derived d = new Derived();
- System.out.println( d.whenAmISet );
- }
- }
再續(xù)繼往下閱讀之前,請先給自己一些時間想一下上面的這段程序的輸出是什么?是的,這看起來的確相當(dāng)簡單,甚至不需要編譯和運行上面的代碼,我們也應(yīng)該知道其答案,那么,你覺得你知道答案嗎?你確定你的答案正確嗎?
很多人都會覺得那段程序的輸出應(yīng)該是“set in preProcess()”,這是因為當(dāng)子類Derived 的構(gòu)造函數(shù)被調(diào)用時,其會隱晦地調(diào)用其基類Base的構(gòu)造函數(shù)(通過super()函數(shù)),于是基類Base的構(gòu)造函數(shù)會調(diào)用preProcess() 函數(shù),因為這個類的實例是Derived的,而且在子類Derived中對這個函數(shù)使用了override關(guān)鍵字,所以,實際上調(diào)用到的是:Derived.preProcess(),而這個方法設(shè)置了whenAmISet 成員變量的值為:“set in preProcess()”。
當(dāng)然,上面的結(jié)論是錯誤的。如果你編譯并運行這個程序,你會發(fā)現(xiàn),程序?qū)嶋H輸出的是“set when declared ”。怎么為這樣呢?難道是基類Base 的preProcess() 方法被調(diào)用啦?也不是!你可以在基類的preProcess中輸出點什么看看,你會發(fā)現(xiàn)程序運行時,Base.preProcess()并沒有被調(diào)用到(不然這對于Java所有的應(yīng)用程序?qū)且粋€***災(zāi)難性的Bug)。
雖然上面的結(jié)論是錯誤的,但推導(dǎo)過程是合理的,只是不完整,下面是整個運行的流程:
◆進入Derived 構(gòu)造函數(shù)。
◆Derived 成員變量的內(nèi)存被分配。
◆Base 構(gòu)造函數(shù)被隱含調(diào)用。
◆Base 構(gòu)造函數(shù)調(diào)用preProcess()。
◆Derived 的preProcess 設(shè)置whenAmISet 值為 “set in preProcess()”。
◆Derived 的成員變量初始化被調(diào)用。
◆執(zhí)行Derived 構(gòu)造函數(shù)體。
等一等,這怎么可能?在第6步,Derived 成員的初始化居然在 preProcess() 調(diào)用之后?是的,正是這樣,我們不能讓成員變量的聲明和初始化變成一個原子操作,雖然在Java中我們可以把其寫在一起,讓其看上去像是聲明和初始化一體。但這只是假象,我們的錯誤就在在我們把Java的聲明和初始化看成了一體。在C++的世界中,C++并不支持成員變量在聲明的時候進行初始化,其需要你在構(gòu)造函數(shù)中顯式的初始化其成員變量的值,看起來很土,但其實C++用心良苦。
在面向?qū)ο蟮氖澜缰?,因為程序以對象的形式出現(xiàn),導(dǎo)致了我們對程序執(zhí)行的順序霧里看花。所以,在面向?qū)ο蟮氖澜缰校绦驁?zhí)行的順序相當(dāng)?shù)闹匾?/P>
下面是對上面各個步驟的逐條解釋。
◆進入構(gòu)造函數(shù)。
◆為成員變量分配內(nèi)存。
◆除非你顯式地調(diào)用super(),否則Java 會在子類的構(gòu)造函數(shù)最前面偷偷地插入super() 。
◆調(diào)用父類構(gòu)造函數(shù)。
◆調(diào)用preProcess,因為被子類override,所以調(diào)用的是子類的。
◆于是,初始化發(fā)生在了preProcess()之后。這是因為,Java需要保證父類的初始化早于子類的成員初始化,否則,在子類中使用父類的成員變量就會出現(xiàn)問題。
◆正式執(zhí)行子類的構(gòu)造函數(shù)(當(dāng)然這是一個空函數(shù),居然我們沒有聲明)。
你可以查看《Java語言的規(guī)格說明書》中的 相關(guān)章節(jié) 來了解更多的Java創(chuàng)建對象時的細(xì)節(jié)。
***,需要向大家推薦一本書,Joshua Bloch 和 Neal Gafter 寫的 Java Puzzlers: Traps, Pitfalls, and Corner Cases,中文版《JAVA解惑》。
【編輯推薦】


2011-07-22 17:46:43
2009-08-26 18:28:44




