程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Think in Java之構造器的真正調用順序

Think in Java之構造器的真正調用順序

編輯:關於JAVA

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

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

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

控制台的打印結果為:

meal 
Lunch 
PortableLunch 
Bread 
Cheese 
Lettuce 
Sandwich

復雜對象調用構造器的順序應該遵循下面的原則:

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

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

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

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

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

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

深入探究:

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

控制台打印結果:

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方法,這些方法不能被覆蓋,因此也就不會出現令人驚訝的問題。

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

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved