第五章 品味Java子類型多態的魅力(1 / 3)

第五章 品味Java子類型多態的魅力

"polymorphism(多態)"一詞來自希臘語,意為"多種形式"。多數Java程序員把多態看作對象的一種能力,使其能調用正確的方法版本。盡管如此,這種麵向實現的觀點導致了多態的神奇功能,勝於僅僅把多態看成純粹的概念。

Java中的多態總是子類型的多態。幾乎是機械式產生了一些多態的行為,使我們不去考慮其中涉及的類型問題。本文研究了一種麵向類型的對象觀點,分析了如何將對象能夠表現的行為和對象即將表現的行為分離開來。拋開Java中的多態都是來自繼承的概念,我們仍然可以感到,Java中的接口是一組沒有公共代碼的對象共享實現。

多態的分類

多態在麵向對象語言中是個很普遍的概念.雖然我們經常把多態混為一談,但實際上有四種不同類型的多態。在開始正式的子類型多態的細節討論前,然我們先來看看普通麵向對象中的多態。

Luca Cardelli和Peter Wegner("On Understanding Types, Data Abstraction, and Polymorphism"一文的作者, 文章參考資源鏈接)把多態分為兩大類----特定的和通用的----四小類:強製的,重載的,參數的和包含的。他們的結構如下:

在這樣一個體係中,多態表現出多種形式的能力。通用多態引用有相同結構類型的大量對象,他們有著共同的特征。特定的多態涉及的是小部分沒有相同特征的對象。四種多態可做以下描述:

強製的:一種隱式做類型轉換的方法。

重載的:將一個標誌符用作多個意義。

參數的:為不同類型的參數提供相同的操作。

包含的:類包含關係的抽象操作。

我將在講述子類型多態前簡單介紹一下這幾種多態。

強製的多態

強製多態隱式的將參數按某種方法,轉換成編譯器認為正確的類型以避免錯誤。在以下的表達式中,編譯器必須決定二元運算符'+'所應做的工作:

2.0 + 2.0

2.0 + 2

2.0 + "2"

第一個表達式將兩個double的操作數相加;Java中特別聲明了這種用法。

第二個表達式將double型和int相加。Java中沒有明確定義這種運算。不過,編譯器隱式的將第二個操作數轉換為double型,並作double型的加法。做對程序員來說十分方便,否則將會拋出一個編譯錯誤,或者強製程序員顯式的將int轉換為double。

第三個表達式將double與一個String相加。Java中同樣沒有定義這樣的操作。所以,編譯器將double轉換成String類型,並將他們做串聯。

強製多態也會發生在方法調用中。假設類Derived繼承了類Base,類C有一個方法,原型為m(Base),在下麵的代碼中,編譯器隱式的將Derived類的對象derived轉化為Base類的對象。這種隱式的轉換使m(Base)方法使用所有能轉換成Base類的所有參數。

C c = new C();

Derived derived = new Derived();

c.m( derived );

並且,隱式的強製轉換,可以避免類型轉換的麻煩,減少編譯錯誤。當然,編譯器仍然會優先驗證符合定義的對象類型。

重載的多態

重載允許用相同的運算符或方法,去表示截然不同的意義。'+'在上麵的程序中有兩個意思:兩個double型的數相加;兩個串相連。另外還有整型相加,長整型,等等。這些運算符的重載,依賴於編譯器根據上下文做出的選擇。以往的編譯器會把操作數隱式轉換為完全符合操作符的類型。雖然Java明確支持重載,但不支持用戶定義的操作符重載。

Java支持用戶定義的函數重載。一個類中可以有相同名字的方法,這些方法可以有不同的意義。這些重載的方法中,必須滿足參數數目不同,相同位置上的參數類型不同。這些不同可以幫助編譯器區分不同版本的方法。

編譯器以這種唯一表示的特征來表示不同的方法,比用名字表示更為有效。據此,所有的多態行為都能編譯通過。

強製和重載的多態都被分類為特定的多態,因為這些多態都是在特定的意義上的。這些被劃入多態的特性給程序員帶來了很大的方便。強製多態排除了麻煩的類型和編譯錯誤。重載多態像一塊糖,允許程序員用相同的名字表示不同的方法,很方便。

參數的多態

參數多態允許把許多類型抽象成單一的表示。例如,List抽象類中,描述了一組具有同樣特征的對象,提供了一個通用的模板。你可以通過指定一種類型以重用這個抽象類。這些參數可以是任何用戶定義的類型,大量的用戶可以使用這個抽象類,因此參數多態毫無疑問的成為最強大的多態。

乍一看,上麵抽象類好像是java.util.List的功能。然而,Java實際上並不支持真正的安全類型風格的參數多態,這也是java.util.List和java.util的其他集合類是用原始的java.lang.Object寫的原因(參考我的文章"A Primordial Interface?" 以獲得更多細節)。Java的單根繼承方式解決了部分問題,但沒有發揮出參數多態的全部功能。Eric Allen有一篇精彩的文章"Behold the Power of Parametric Polymorphism",描述了Java通用類型的需求,並建議給Sun的Java規格需求#000014號文檔"Add Generic Types to the Java Programming Language."(參考資源鏈接)

包含的多態

包含多態通過值的類型和集合的包含關係實現了多態的行為.在包括Java在內的眾多麵向對象語言中,包含關係是子類型的。所以,Java的包含多態是子類型的多態。

在早期,Java開發者們所提及的多態就特指子類型的多態。通過一種麵向類型的觀點,我們可以看到子類型多態的強大功能。以下的文章中我們將仔細探討這個問題。為簡明起見,下文中的多態均指包含多態。

麵向類型觀點

圖1的UML類圖給出了類和類型的簡單繼承關係,以便於解釋多態機製。模型中包含5種類型,4個類和一個接口。雖然UML中稱為類圖,我把它看成類型圖。如"Thanks Type and Gentle Class," 一文中所述,每個類和接口都是一種用戶定義的類型。按獨立實現的觀點(如麵向類型的觀點),下圖中的每個矩形代表一種類型。從實現方法看,四種類型運用了類的結構,一種運用了接口的結構。

圖1:示範代碼的UML類圖

以下的代碼實現了每個用戶定義的數據類型,我把實現寫得很簡單。

/* Base.java */

public class Base

{

public String m1()

{

return "Base.m1()";

}

public String m2( String s )

{

return "Base.m2( " + s + " )";

}

}

/* IType.java */

interface IType

{

String m2( String s );

String m3();

}

/* Derived.java */

public class Derived

extends Base

implements IType

{

public String m1()

{

return "Derived.m1()";

}