think in java之構造器的真正調用順序

原文:http://blog.csdn.net/yaerfeng/article/details/7294882


構造器是OOP的重要組成部分,很多人認爲它很容易。只不過是new了一個對象而已。而think in java的作者卻告訴我們,其實這並不容易。

先看下面這個例子。在你沒看結果之前,你覺得你的答案是對的麼。

[java] view plaincopy
  1. package com.tudou.t1;  
  2.   
  3. class Meal {  
  4.     Meal() {  
  5.         System.out.println("meal");  
  6.     }  
  7. }  
  8.   
  9. class Bread {  
  10.     Bread() {  
  11.         System.out.println("Bread");  
  12.     }  
  13. }  
  14.   
  15. class Cheese {  
  16.     Cheese() {  
  17.         System.out.println("Cheese");  
  18.     }  
  19. }  
  20.   
  21. class Lettuce {  
  22.     Lettuce() {  
  23.         System.out.println("Lettuce");  
  24.     }  
  25. }  
  26.   
  27. class Lunch extends Meal{  
  28.     Lunch() {  
  29.         System.out.println("Lunch");  
  30.     }  
  31. }  
  32.   
  33. class PortableLunch extends Lunch{  
  34.     PortableLunch() {  
  35.         System.out.println("PortableLunch");  
  36.     }  
  37. }  
  38.   
  39. public class Sandwich extends PortableLunch {  
  40.     private Bread b = new Bread();  
  41.     private Cheese c = new Cheese();  
  42.     private Lettuce l = new Lettuce();  
  43.   
  44.     public Sandwich() {  
  45.         System.out.println("Sandwich");  
  46.     }  
  47.   
  48.     public static void main(String[] args) {  
  49.         new Sandwich();  
  50.     }  
  51. }  

控制檯的打印結果爲:

meal
Lunch
PortableLunch
Bread
Cheese
Lettuce
Sandwich



複雜對象調用構造器的順序應該遵循下面的原則:
1,調用基類[即父類]構造器。這個步驟會不斷反覆遞歸下去,首先是構造器這種層次結構的根,然後是下一層導出類[即子類],等等。直到最底層的導出類。[從最上層的meal一直遞歸到PortableLunch]

2,按聲明順序調用成員的初始化方法。[即上面的Bread,Cheese,Lettuce]

3,調用導出類構造器的主體[即Sandwich]

可見,調用類本身是最後完成初始化的,最先完成初始化的是最頂級的基類,所謂沒有父親,哪來的兒子。處於它們中間的是調用類本身擁有的子對象。因爲你不可能在子對象初始化之前用本類調用它,所以它一定在本類調用之前,父類調用之後完成初始化的。


那麼這個說法是不是一定成立呢。結果是否定的。你必須知道JVM的編繹原理纔可能知道,它究竟是如何工作的。

我們來看下面這個例子,來解釋爲什麼它不一定。因爲在繼承和重寫的時候,這種情況變得有點詭異。

深入探究:

[java] view plaincopy
  1. package com.tudou.t1;  
  2.   
  3. public class ConstrcutorTest2 {  
  4.     public static void main(String[] args) {  
  5.         new RoundGlyph(5);  
  6.     }  
  7. }  
  8.   
  9. class Glyph {  
  10.   
  11.     void draw() {  
  12.         System.out.println("Glyph draw()");  
  13.     }  
  14.   
  15.     Glyph() {  
  16.         System.out.println("Glyph before draw();");  
  17.         draw();  
  18.         System.out.println("Glyph after draw();");  
  19.     }  
  20. }  
  21.   
  22. class RoundGlyph extends Glyph {  
  23.     private int radius = 1;  
  24.   
  25.     RoundGlyph(int r) {  
  26.         radius = r;  
  27.         System.out.println("RoundGlyph(),radius:" + radius);  
  28.     }  
  29.   
  30.     void draw() {  
  31.         System.out.println("RoundGlyph.draw(),radius:" + radius);//此處打印是0,而不是1  
  32.     }  
  33. }  

控制檯打印結果:

Glyph before draw();
RoundGlyph.draw(),radius:0
Glyph after draw();
RoundGlyph(),radius:5

爲什麼RoundGlyph.draw(),radius:0這裏會是0呢。

默認的1哪去了?值自己會變麼。其實上面的講述並不完整。,而這正是解決謎題的關鍵所在。初始化的實際過程之前,實際在還有一步。


0:在其他任何事物發生之前,將分配對象的存捨得空間初始化爲二進制的零。

而它後面的初始化順序就是上面的3步。

1,調用基類[即父類]構造器。這個步驟會不斷反覆遞歸下去,首先是構造器這種層次結構的根,然後是下一層導出類[即子類],等等。直到最底層的導出類。

2,按聲明順序調用成員的初始化方法。

3,調用導出類構造器的主體



也就是說,實際上有4步,知道這些你對對象初始化構造器纔可能有個清楚的認識。

JAVA有更多的精髓等着人們去挖掘,而不僅僅是知道如何去使用它。

因爲你不知道什麼時候它會出現意想不到的後果,而這個錯誤,可能你根本就想不出來。


編寫構造器時有一條準則:

用儘可能簡單的方法使對象進入正常狀態,如果可以的話,避免調用其它方法。


在構造器內唯一能夠安全調用的那些方法是基類中的final或者private方法,這些方法不能被覆蓋,因此也就不會出現令人驚訝的問題。

你可能無法總是遵循這條準則,但是應該朝着它努力。


學任何語言,請打好基礎,它是你以後擴展的人生基石。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章