程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 構建器內部的多形性方法的行為

構建器內部的多形性方法的行為

編輯:關於JAVA

構建器調用的分級結構(順序)為我們帶來了一個有趣的問題,或者說讓我們進入了一種進退兩難的局面。若當前位於一個構建器的內部,同時調用准備構建的那個對象的一個動態綁定方法,那麼會出現什麼情況呢?在原始的方法內部,我們完全可以想象會發生什麼——動態綁定的調用會在運行期間進行解析,因為對象不知道它到底從屬於方法所在的那個類,還是從屬於從它衍生出來的某些類。為保持一致性,大家也許會認為這應該在構建器內部發生。
但實際情況並非完全如此。若調用構建器內部一個動態綁定的方法,會使用那個方法被覆蓋的定義。然而,產生的效果可能並不如我們所願,而且可能造成一些難於發現的程序錯誤。
從概念上講,構建器的職責是讓對象實際進入存在狀態。在任何構建器內部,整個對象可能只是得到部分組織——我們只知道基礎類對象已得到初始化,但卻不知道哪些類已經繼承。然而,一個動態綁定的方法調用卻會在分級結構裡“向前”或者“向外”前進。它調用位於衍生類裡的一個方法。如果在構建器內部做這件事情,那麼對於調用的方法,它要操縱的成員可能尚未得到正確的初始化——這顯然不是我們所希望的。
通過觀察下面這個例子,這個問題便會昭然若揭:
 

//: PolyConstructors.java
// Constructors and polymorphism
// don't produce what you might expect.

abstract class Glyph {
  abstract void draw();
  Glyph() {
    System.out.println("Glyph() before draw()");
    draw(); 
    System.out.println("Glyph() after draw()");
  }
}

class RoundGlyph extends Glyph {
  int radius = 1;
  RoundGlyph(int r) {
    radius = r;
    System.out.println(
      "RoundGlyph.RoundGlyph(), radius = "
      + radius);
  }
  void draw() { 
    System.out.println(
      "RoundGlyph.draw(), radius = " + radius);
  }
}

public class PolyConstructors {
  public static void main(String[] args) {
    new RoundGlyph(5);
  }
} ///:~


在Glyph中,draw()方法是“抽象的”(abstract),所以它可以被其他方法覆蓋。事實上,我們在RoundGlyph中不得不對其進行覆蓋。但Glyph構建器會調用這個方法,而且調用會在RoundGlyph.draw()中止,這看起來似乎是有意的。但請看看輸出結果:
 

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

當Glyph的構建器調用draw()時,radius的值甚至不是默認的初始值1,而是0。這可能是由於一個點號或者屏幕上根本什麼都沒有畫而造成的。這樣就不得不開始查找程序中的錯誤,試著找出程序不能工作的原因。
前一節講述的初始化順序並不十分完整,而那是解決問題的關鍵所在。初始化的實際過程是這樣的:
(1) 在采取其他任何操作之前,為對象分配的存儲空間初始化成二進制零。
(2) 就象前面敘述的那樣,調用基礎類構建器。此時,被覆蓋的draw()方法會得到調用(的確是在RoundGlyph構建器調用之前),此時會發現radius的值為0,這是由於步驟(1)造成的。
(3) 按照原先聲明的順序調用成員初始化代碼。
(4) 調用衍生類構建器的主體。

采取這些操作要求有一個前提,那就是所有東西都至少要初始化成零(或者某些特殊數據類型與“零”等價的值),而不是僅僅留作垃圾。其中包括通過“合成”技術嵌入一個類內部的對象句柄。如果假若忘記初始化那個句柄,就會在運行期間出現違例事件。其他所有東西都會變成零,這在觀看結果時通常是一個嚴重的警告信號。
在另一方面,應對這個程序的結果提高警惕。從邏輯的角度說,我們似乎已進行了無懈可擊的設計,所以它的錯誤行為令人非常不可思議。而且沒有從編譯器那裡收到任何報錯信息(C++在這種情況下會表現出更合理的行為)。象這樣的錯誤會很輕易地被人忽略,而且要花很長的時間才能找出。
因此,設計構建器時一個特別有效的規則是:用盡可能簡單的方法使對象進入就緒狀態;如果可能,避免調用任何方法。在構建器內唯一能夠安全調用的是在基礎類中具有final屬性的那些方法(也適用於private方法,它們自動具有final屬性)。這些方法不能被覆蓋,所以不會出現上述潛在的問題。

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