何時創(chuàng)建Java對象實例
Java對象實例何時被創(chuàng)建,這個問題也許你用一句話就能回答完了。但是它的潛在陷阱卻常常被人忽視,這個問題也許并不像你想的那么簡單,不信請你耐心看下去。
我前幾天問一個同學(xué),是不是在調(diào)用構(gòu)造函數(shù)后,對象才被實例化?他不假思索的回答說是。
請看下面代碼:
- Date date=new Date();
- em.out.println(date.getTime());
新手在剛接觸構(gòu)造函數(shù)這個概念的時候。他們常常得出這樣的結(jié)論:對象實例是在調(diào)用構(gòu)造函數(shù)后創(chuàng)建的。因為調(diào)用構(gòu)造函數(shù)后,調(diào)用引用(date)的實例方法便不會報NullPointerException的錯誤了。
一、經(jīng)驗者的觀點
然而,稍稍有經(jīng)驗的Java程序員便會發(fā)現(xiàn)上面的解釋并不正確。這點從構(gòu)造函數(shù)中我們可以調(diào)用this關(guān)鍵字可以看出。
請看下面代碼:
- public class Test
- {
- public Test()
- {
- this.DoSomething();
- }
- private void DoSomething()
- {
- System.out.println("do init");
- }
- }
這段代碼中我們在構(gòu)造函數(shù)中已經(jīng)可以操作對象實例。這也就證明了構(gòu)造函數(shù)其實只是用于初始化,早在進入構(gòu)造函數(shù)之前。對象實例便已經(jīng)被創(chuàng)建了。
二、父類構(gòu)造函數(shù)
當(dāng)創(chuàng)建一個有父類的子類的時候。對象的實例又是何時被創(chuàng)建的呢?我們也許接觸過下面經(jīng)典的代碼:
- public class BaseClass
- {
- public BaseClass()
- {
- System.out.println("create base");
- }
- }
- public class SubClass
- {
- public SubClass()
- {
- System.out.println("create sub");
- }
- public static void main(String[] args)
- {
- new SubClass();
- }
- }
結(jié)果是先輸出create base,后輸出create sub。這個結(jié)果看起來和現(xiàn)實世界完全一致,先有老爸,再有兒子。因此我相信有很多程序員跟我一樣會認(rèn)為new SubClass()的過程是:實例化BaseClass->調(diào)用BaseClass構(gòu)造函數(shù)初始化->實例化SubClass->調(diào)用SubClass構(gòu)造函數(shù)初始化。然而非常不幸的是,這是個錯誤的觀點。
三、奇怪的代碼
以下代碼是為了駁斥上面提到的錯誤觀點。但是這種代碼其實在工作中甚少出現(xiàn)。
- public class BaseClass
- {
- public BaseClass()
- {
- System.out.println("create base");
- init();
- }
- protected void init()
- {
- System.out.println("do init");
- }
- }
- public class SubClass
- {
- public SubClass()
- {
- System.out.println("create sub");
- }
- @Override
- protected void init()
- {
- assert this!=null;
- System.out.println("now the working class is:"+this.getClass().getSimpleName());
- System.out.println("in SubClass");
- }
- public static void main(String[] args)
- {
- new SubClass();
- }
- }
這段代碼運行的結(jié)果是先調(diào)用父類的構(gòu)造函數(shù),再調(diào)用子類的init()方法,再調(diào)用子類的構(gòu)造函數(shù)。
這是一段奇妙的代碼,子類的構(gòu)造函數(shù)居然不是子類第一個被執(zhí)行的方法。我們早已習(xí)慣于通過super方便的調(diào)用父類的方法,但是好像從沒這樣嘗試從父類調(diào)用子類的方法。
再次聲明,這只是個示例。是為了與您一起探討對象實例化的秘密。通過這個示例,我們再次印證了開頭的觀點:早在構(gòu)造函數(shù)被調(diào)用之前,實例便已被創(chuàng)造。若該對象有父類,則早在父類的構(gòu)造函數(shù)被調(diào)用之前,實例也已被創(chuàng)造。這讓java顯得有些不面向?qū)ο螅瓉砝献觾鹤悠鋵嵤且粔K兒出生的。
四、奇怪但危險的代碼
本篇是對上篇奇怪代碼的延續(xù)。但是這段代碼更加具有疑惑性,理解不當(dāng)將會讓你出現(xiàn)致命失誤。
請看下面代碼:
- public class BaseClass {
- public BaseClass()
- {
- System.out.println("create base");
- init();
- }
- protected void init() {
- System.out.println("in base init");
- }
- }
- public class SubClass extends BaseClass{
- int i=1024;
- String s="13 leaf";
- public SubClass()
- {
- System.out.println("create sub");
- init();
- }
- @Override
- protected void init() {
- assert this!=null;
- System.out.println("now the working class is:"+this.getClass().getSimpleName());
- System.out.println("in SubClass");
- /////////////great line/////////////////
- System.out.println(i);
- System.out.println(s);
- }
- public static void main(String[] args) {
- new SubClass();
- //oh!my god!!
- }
- }
這段代碼相比上一篇,只是在子類中添加了一些成員變量。而我們的目標(biāo)正是集中在討論成員變量初始化的問題上。
這段代碼的執(zhí)行順序是:父類、子類實例化->調(diào)用父類構(gòu)造函數(shù)->調(diào)用子類init()方法->調(diào)用子類構(gòu)造函數(shù)->調(diào)用子類init()方法。最終的輸出結(jié)果向我們揭示了成員變量初始化的秘密。
當(dāng)父類構(gòu)造函數(shù)調(diào)用子類的init()方法的時候。子類的成員變量統(tǒng)統(tǒng)是空的,這個空是指的低級初始化。(值類型為0,布爾類型為false,引用類型為null)。而當(dāng)子類構(gòu)造函數(shù)調(diào)用init()方法的時候,成員變量才真正被初始化。這是一個危險的訊息,那就是使用父類構(gòu)造函數(shù)調(diào)用子類時存在成員變量未初始化的風(fēng)險。
我們的討論也到此為止了。再次回顧,總結(jié)一下實例何時被創(chuàng)建這個問題。我得出了以下結(jié)論:
本文到此便結(jié)束了。鑒于本人才疏學(xué)淺,若是專業(yè)術(shù)語有錯誤,或是哪里講的不對,也歡迎各位高手拍磚。
【編輯推薦】