程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 對象實例是何時被創建的?

對象實例是何時被創建的?

編輯:關於JAVA

對象實例何時被創建,這個問題也許你用一句話就能回答完了。但是它的潛在陷阱卻常常被人忽視,這個問題也許並不像你想的那麼簡單,不信請你耐心看下去。

我前幾天問一個同學,是不是在調用構造函數後,對象才被實例化?他不假思索的回答說是。

請看下面代碼:

Java代碼

  1. Date date=new Date();
  2. em.out.println(date.getTime());

新手在剛接觸構造函數這個概念的時候。他們常常得出這樣的結論:對象實例是在調用構造函數後創建的。因為調用構造函數後,調用引用(date)的實例方法便不會報NullPointerException的錯誤了。

二、經驗者的觀點

然而,稍稍有經驗的Java程序員便會發現上面的解釋並不正確。這點從構造函數中我們可以調用this關鍵字可以看出。

請看下面代碼:

Java代碼

  1. public class Test
  2. {
  3. public Test()
  4. {
  5. this.DOSomething();
  6. }
  7. private void DOSomething()
  8. {
  9. System.out.println("do init");
  10. }
  11. }

這段代碼中我們在構造函數中已經可以操作對象實例。這也就證明了構造函數其實只是用於初始化,早在進入構造函數之前。對象實例便已經被創建了。

三、父類構造函數

當創建一個有父類的子類的時候。對象的實例又是何時被創建的呢?我們也許接觸過下面經典的代碼:

Java代碼

  1. public class BaseClass
  2. {
  3. public BaseClass()
  4. {
  5. System.out.println("create base");
  6. }
  7. }
  8. public class SubClass
  9. {
  10. public SubClass()
  11. {
  12. System.out.println("create sub");
  13. }
  14. public static void main(String[] args)
  15. {
  16. new SubClass();
  17. }
  18. }

結果是先輸出create base,後輸出create sub。這個結果看起來和現實世界完全一致,先有老爸,再有兒子。因此我相信有很多程序員跟我一樣會認為new SubClass()的過程是:實例化BaseClass->調用BaseClass構造函數初始化->實例化SubClass->調用SubClass構造函數初始化。然而非常不幸的是,這是個錯誤的觀點。

四、奇怪的代碼

以下代碼是為了駁斥上面提到的錯誤觀點。但是這種代碼其實在工作中甚少出現。

Java代碼

  1. public class BaseClass
  2. {
  3. public BaseClass()
  4. {
  5. System.out.println("create base");
  6. init();
  7. }
  8. protected void init() {
  9. System.out.println("do init");
  10. }
  11. }
  12. //
  13. public class SubClass
  14. {
  15. public SubClass()
  16. {
  17. System.out.println("create sub");
  18. }
  19. @Override
  20. protected void init()
  21. {
  22. assert this!=null;
  23. System.out.println("now the working class is:"+this.getClass().getSimpleName());
  24. System.out.println("in SubClass");
  25. }
  26. public static void main(String[] args)
  27. {
  28. new SubClass();
  29. }
  30. }

這段代碼運行的結果是先調用父類的構造函數,再調用子類的init()方法,再調用子類的構造函數。

這是一段奇妙的代碼,子類的構造函數居然不是子類第一個被執行的方法。我們早已習慣於通過super方便的調用父類的方法,但是好像從沒這樣嘗試從父類調用子類的方法。

再次聲明,這只是個示例。是為了與您一起探討對象實例化的秘密。通過這個示例,我們再次印證了開頭的觀點:早在構造函數被調用之前,實例便已被創造。若該對象有父類,則早在父類的構造函數被調用之前,實例也已被創造。這讓Java顯得有些不面向對象,原來老子兒子其實是一塊兒出生的。

五、奇怪但危險的代碼

本篇是對上篇奇怪代碼的延續。但是這段代碼更加具有疑惑性,理解不當將會讓你出現致命失誤。

請看下面代碼:

Java代碼

  1. public class BaseClass {
  2. public BaseClass()
  3. {
  4. System.out.println("create base");
  5. init();
  6. }
  7. protected void init() {
  8. System.out.println("in base init");
  9. }
  10. }
  11. public class SubClass extends BaseClass{
  12. int i=1024;
  13. String s="13 leaf";
  14. public SubClass()
  15. {
  16. System.out.println("create sub");
  17. init();
  18. }
  19. @Override
  20. protected void init() {
  21. assert this!=null;
  22. System.out.println("now the working class is:"+this.getClass().getSimpleName());
  23. System.out.println("in SubClass");
  24. /////////////great line/////////////////
  25. System.out.println(i);
  26. System.out.println(s);
  27. }
  28. public static void main(String[] args) {
  29. new SubClass();
  30. //oh!my god!!
  31. }
  32. }

這段代碼相比上一篇,只是在子類中添加了一些成員變量。而我們的目標正是集中在討論成員變量初始化的問題上。

這段代碼的執行順序是:父類、子類實例化->調用父類構造函數->調用子類init()方法->調用子類構造函數->調用子類init()方法。最終的輸出結果向我們揭示了成員變量初始化的秘密。

當父類構造函數調用子類的init()方法的時候。子類的成員變量統統是空的,這個空是指的低級初始化。(值類型為0,布爾類型為false,引用類型為null)。而當子類構造函數調用init()方法的時候,成員變量才真正被初始化。這是一個危險的訊息,那就是使用父類構造函數調用子類時存在成員變量未初始化的風險。

我們的討論也到此為止了。再次回顧,總結一下實例何時被創建這個問題。我得出了以下結論:

本文到此便結束了。鑒於本人才疏學淺,若是專業術語有錯誤,或是哪裡講的不對,也歡迎各位高手拍磚。

附上第五篇中SubClass的部分字節碼,方便大家深入理解:

Java代碼

  1. public SubClass();
  2. aload_0 [this] //aload_0是啥?
  3. invokespecial ques.BaseClass() [26] //調用父類構造函數
  4. aload_0 [this]
  5. sipush 1024 //初始化i成員變量
  6. putfIEld ques.SubClass.i : int [28]
  7. aload_0 [this]
  8. ldc "13 leaf"> [30] //初始化s成員變量
  9. putfIEld ques.SubClass.s : Java.lang.String [32]
  10. getstatic java.lang.System.out : Java.io.PrintStream [34]
  11. ldc "create sub"> [40]
  12. invokevirtual java.io.PrintStream.println(Java.lang.String) : void [42]
  13. aload_0 [this]
  14. invokevirtual ques.SubClass.init() : void [48] //調用init
  15. return
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved