知道Java裡綁定的所有方法都通過後期綁定具有多形性以後,就可以相應地編寫自己的代碼,令其與基礎類溝通。此時,所有的衍生類都保證能用相同的代碼正常地工作。或者換用另一種方法,我們可以“將一條消息發給一個對象,讓對象自行判斷要做什麼事情。”
在面向對象的程序設計中,有一個經典的“形狀”例子。由於它很容易用可視化的形式表現出來,所以經常都用它說明問題。但很不幸的是,它可能誤導初學者認為OOP只是為圖形化編程設計的,這種認識當然是錯誤的。
形狀例子有一個基礎類,名為Shape;另外還有大量衍生類型:Circle(圓形),Square(方形),Triangle(三角形)等等。大家之所以喜歡這個例子,因為很容易理解“圓屬於形狀的一種類型”等概念。下面這幅繼承圖向我們展示了它們的關系:
上溯造型可用下面這個語句簡單地表現出來:
Shape s = new Circle();
在這裡,我們創建了Circle對象,並將結果句柄立即賦給一個Shape。這表面看起來似乎屬於錯誤操作(將一種類型分配給另一個),但實際是完全可行的——因為按照繼承關系,Circle屬於Shape的一種。因此編譯器認可上述語句,不會向我們提示一條出錯消息。
當我們調用其中一個基礎類方法時(已在衍生類裡覆蓋):
s.draw();
同樣地,大家也許認為會調用Shape的draw(),因為這畢竟是一個Shape句柄。那麼編譯器怎樣才能知道該做其他任何事情呢?但此時實際調用的是Circle.draw(),因為後期綁定已經介入(多形性)。
下面這個例子從一個稍微不同的角度說明了問題:
//: Shapes.java // Polymorphism in Java class Shape { void draw() {} void erase() {} } class Circle extends Shape { void draw() { System.out.println("Circle.draw()"); } void erase() { System.out.println("Circle.erase()"); } } class Square extends Shape { void draw() { System.out.println("Square.draw()"); } void erase() { System.out.println("Square.erase()"); } } class Triangle extends Shape { void draw() { System.out.println("Triangle.draw()"); } void erase() { System.out.println("Triangle.erase()"); } } public class Shapes { public static Shape randShape() { switch((int)(Math.random() * 3)) { default: // To quiet the compiler case 0: return new Circle(); case 1: return new Square(); case 2: return new Triangle(); } } public static void main(String[] args) { Shape[] s = new Shape[9]; // Fill up the array with shapes: for(int i = 0; i < s.length; i++) s[i] = randShape(); // Make polymorphic method calls: for(int i = 0; i < s.length; i++) s[i].draw(); } } ///:~
針對從Shape衍生出來的所有東西,Shape建立了一個通用接口——也就是說,所有(幾何)形狀都可以描繪和刪除。衍生類覆蓋了這些定義,為每種特殊類型的幾何形狀都提供了獨一無二的行為。
在主類Shapes裡,包含了一個static方法,名為randShape()。它的作用是在每次調用它時為某個隨機選擇的Shape對象生成一個句柄。請注意上溯造型是在每個return語句裡發生的。這個語句取得指向一個Circle,Square或者Triangle的句柄,並將其作為返回類型Shape發給方法。所以無論什麼時候調用這個方法,就絕對沒機會了解它的具體類型到底是什麼,因為肯定會獲得一個單純的Shape句柄。
main()包含了Shape句柄的一個數組,其中的數據通過對randShape()的調用填入。在這個時候,我們知道自己擁有Shape,但不知除此之外任何具體的情況(編譯器同樣不知)。然而,當我們在這個數組裡步進,並為每個元素調用draw()的時候,與各類型有關的正確行為會魔術般地發生,就象下面這個輸出示例展示的那樣:
Circle.draw() Triangle.draw() Circle.draw() Circle.draw() Circle.draw() Square.draw() Triangle.draw() Square.draw() Square.draw()
當然,由於幾何形狀是每次隨機選擇的,所以每次運行都可能有不同的結果。之所以要突出形狀的隨機選擇,是為了讓大家深刻體會這一點:為了在編譯的時候發出正確的調用,編譯器毋需獲得任何特殊的情報。對draw()的所有調用都是通過動態綁定進行的。