“polymorphism(多態(tài))”一詞來(lái)自希臘語(yǔ),意為“多種形式”。多數(shù)Java程序員把多態(tài)看作對(duì)象的一種能力,使其能調(diào)用正確的方法版本。盡管如此,這種面向?qū)崿F(xiàn)的觀點(diǎn)導(dǎo)致了多態(tài)的神奇功能,勝于僅僅把多態(tài)看成純粹的概念。
Java中的多態(tài)總是子類型的多態(tài)。幾乎是機(jī)械式產(chǎn)生了一些多態(tài)的行為,使我們不去考慮其中涉及的類型問(wèn)題。本文研究了一種面向類型的對(duì)象觀點(diǎn),分析了如何將對(duì)象能夠表現(xiàn)的行為和對(duì)象即將表現(xiàn)的行為分離開(kāi)來(lái)。拋開(kāi)Java中的多態(tài)都是來(lái)自繼承的概念,我們?nèi)匀豢梢愿械剑琂ava中的接口是一組沒(méi)有公共代碼的對(duì)象共享實(shí)現(xiàn)。
多態(tài)的分類
多態(tài)在面向?qū)ο笳Z(yǔ)言中是個(gè)很普遍的概念.雖然我們經(jīng)常把多態(tài)混為一談,但實(shí)際上有四種不同類型的多態(tài)。在開(kāi)始正式的子類型多態(tài)的細(xì)節(jié)討論前,然我們先來(lái)看看普通面向?qū)ο笾械亩鄳B(tài)。
Luca Cardelli和Peter Wegner("On Understanding Types, Data Abstraction, and Polymorphism"一文的作者, 文章參考資源鏈接)把多態(tài)分為兩大類----特定的和通用的----四小類:強(qiáng)制的,重載的,參數(shù)的和包含的。他們的結(jié)構(gòu)如下:
screen.width-600)this.style.width=screen.width-600;">
在這樣一個(gè)體系中,多態(tài)表現(xiàn)出多種形式的能力。通用多態(tài)引用有相同結(jié)構(gòu)類型的大量對(duì)象,他們有著共同的特征。特定的多態(tài)涉及的是小部分沒(méi)有相同特征的對(duì)象。四種多態(tài)可做以下描述:
強(qiáng)制的:一種隱式做類型轉(zhuǎn)換的方法。
重載的:將一個(gè)標(biāo)志符用作多個(gè)意義。
參數(shù)的:為不同類型的參數(shù)提供相同的操作。
包含的:類包含關(guān)系的抽象操作。
我將在講述子類型多態(tài)前簡(jiǎn)單介紹一下這幾種多態(tài)。
強(qiáng)制的多態(tài)
強(qiáng)制多態(tài)隱式的將參數(shù)按某種方法,轉(zhuǎn)換成編譯器認(rèn)為正確的類型以避免錯(cuò)誤。在以下的表達(dá)式中,編譯器必須決定二元運(yùn)算符‘+’所應(yīng)做的工作:
2.0 + 2.0
2.0 + 2
2.0 + "2"
第一個(gè)表達(dá)式將兩個(gè)double的操作數(shù)相加;Java中特別聲明了這種用法。
第二個(gè)表達(dá)式將double型和int相加。Java中沒(méi)有明確定義這種運(yùn)算。不過(guò),編譯器隱式的將第二個(gè)操作數(shù)轉(zhuǎn)換為double型,并作double型的加法。做對(duì)程序員來(lái)說(shuō)十分方便,否則將會(huì)拋出一個(gè)編譯錯(cuò)誤,或者強(qiáng)制程序員顯式的將int轉(zhuǎn)換為double。
第三個(gè)表達(dá)式將double與一個(gè)String相加。Java中同樣沒(méi)有定義這樣的操作。所以,編譯器將double轉(zhuǎn)換成String類型,并將他們做串聯(lián)。
強(qiáng)制多態(tài)也會(huì)發(fā)生在方法調(diào)用中。假設(shè)類Derived繼承了類Base,類C有一個(gè)方法,原型為m(Base),在下面的代碼中,編譯器隱式的將Derived類的對(duì)象derived轉(zhuǎn)化為Base類的對(duì)象。這種隱式的轉(zhuǎn)換使m(Base)方法使用所有能轉(zhuǎn)換成Base類的所有參數(shù)。
C c = new C();
Derived derived = new Derived();
c.m( derived );
并且,隱式的強(qiáng)制轉(zhuǎn)換,可以避免類型轉(zhuǎn)換的麻煩,減少編譯錯(cuò)誤。當(dāng)然,編譯器仍然會(huì)優(yōu)先驗(yàn)證符合定義的對(duì)象類型。
重載的多態(tài)
重載允許用相同的運(yùn)算符或方法,去表示截然不同的意義。‘+’在上面的程序中有兩個(gè)意思:兩個(gè)double型的數(shù)相加;兩個(gè)串相連。另外還有整型相加,長(zhǎng)整型,等等。這些運(yùn)算符的重載,依賴于編譯器根據(jù)上下文做出的選擇。以往的編譯器會(huì)把操作數(shù)隱式轉(zhuǎn)換為完全符合操作符的類型。雖然Java明確支持重載,但不支持用戶定義的操作符重載。
Java支持用戶定義的函數(shù)重載。一個(gè)類中可以有相同名字的方法,這些方法可以有不同的意義。這些重載的方法中,必須滿足參數(shù)數(shù)目不同,相同位置上的參數(shù)類型不同。這些不同可以幫助編譯器區(qū)分不同版本的方法。
編譯器以這種唯一表示的特征來(lái)表示不同的方法,比用名字表示更為有效。據(jù)此,所有的多態(tài)行為都能編譯通過(guò)。
強(qiáng)制和重載的多態(tài)都被分類為特定的多態(tài),因?yàn)檫@些多態(tài)都是在特定的意義上的。這些被劃入多態(tài)的特性給程序員帶來(lái)了很大的方便。強(qiáng)制多態(tài)排除了麻煩的類型和編譯錯(cuò)誤。重載多態(tài)像一塊糖,允許程序員用相同的名字表示不同的方法,很方便。
參數(shù)的多態(tài)
參數(shù)多態(tài)允許把許多類型抽象成單一的表示。例如,List抽象類中,描述了一組具有同樣特征的對(duì)象,提供了一個(gè)通用的模板。你可以通過(guò)指定一種類型以重用這個(gè)抽象類。這些參數(shù)可以是任何用戶定義的類型,大量的用戶可以使用這個(gè)抽象類,因此參數(shù)多態(tài)毫無(wú)疑問(wèn)的成為最強(qiáng)大的多態(tài)。
乍一看,上面抽象類好像是java.util.List的功能。然而,Java實(shí)際上并不支持真正的安全類型風(fēng)格的參數(shù)多態(tài),這也是java.util.List和java.util的其他集合類是用原始的java.lang.Object寫的原因(參考我的文章"A Primordial Interface?" 以獲得更多細(xì)節(jié))。Java的單根繼承方式解決了部分問(wèn)題,但沒(méi)有發(fā)揮出參數(shù)多態(tài)的全部功能。Eric Allen有一篇精彩的文章“Behold the Power of Parametric Polymorphism”,描述了Java通用類型的需求,并建議給Sun的Java規(guī)格需求#000014號(hào)文檔"Add Generic Types to the Java Programming Language."(參考資源鏈接)
包含的多態(tài)
包含多態(tài)通過(guò)值的類型和集合的包含關(guān)系實(shí)現(xiàn)了多態(tài)的行為.在包括Java在內(nèi)的眾多面向?qū)ο笳Z(yǔ)言中,包含關(guān)系是子類型的。所以,Java的包含多態(tài)是子類型的多態(tài)。
在早期,Java開(kāi)發(fā)者們所提及的多態(tài)就特指子類型的多態(tài)。通過(guò)一種面向類型的觀點(diǎn),我們可以看到子類型多態(tài)的強(qiáng)大功能。以下的文章中我們將仔細(xì)探討這個(gè)問(wèn)題。為簡(jiǎn)明起見(jiàn),下文中的多態(tài)均指包含多態(tài)。
面向類型觀點(diǎn)
圖1的UML類圖給出了類和類型的簡(jiǎn)單繼承關(guān)系,以便于解釋多態(tài)機(jī)制。模型中包含5種類型,4個(gè)類和一個(gè)接口。雖然UML中稱為類圖,我把它看成類型圖。如"Thanks Type and Gentle Class," 一文中所述,每個(gè)類和接口都是一種用戶定義的類型。按獨(dú)立實(shí)現(xiàn)的觀點(diǎn)(如面向類型的觀點(diǎn)),下圖中的每個(gè)矩形代表一種類型。從實(shí)現(xiàn)方法看,四種類型運(yùn)用了類的結(jié)構(gòu),一種運(yùn)用了接口的結(jié)構(gòu)。
screen.width-600)this.style.width=screen.width-600;">
圖1:示范代碼的UML類圖
以下的代碼實(shí)現(xiàn)了每個(gè)用戶定義的數(shù)據(jù)類型,我把實(shí)現(xiàn)寫得很簡(jiǎn)單。
/* 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()"; }
public String m3() { return "Derived.m3()"; } }
/* Derived2.java */
public class Derived2 extends Derived { public String m2( String s ) { return "Derived2.m2( " + s + " )"; } public String m4() { return "Derived2.m4()"; } }
/* Separate.java */
public class Separate implements IType { public String m1() { return "Separate.m1()"; } public String m2( String s ) { return "Separate.m2( " + s + " )"; }
public String m3() { return "Separate.m3()"; } }
|
用這樣的類型聲明和類的定義,圖2從概念的觀點(diǎn)描述了Java指令。
Derived2 derived2 = new Derived2();
screen.width-600)this.style.width=screen.width-600;">
圖2 :Derived2 對(duì)象上的引用
上文中聲明了derived2這個(gè)對(duì)象,它是Derived2類的。圖2種的最頂層把Derived2引用描述成一個(gè)集合的窗口,雖然其下的Derived2對(duì)象是可見(jiàn)的。這里為每個(gè)Derived2類型的操作留了一個(gè)孔。Derived2對(duì)象的每個(gè)操作都去映射適當(dāng)?shù)拇a,按照上面的代碼所描述的那樣。例如,Derived2對(duì)象映射了在Derived中定義的m1()方法。而且還重載了Base類的m1()方法。一個(gè)Derived2的引用變量無(wú)權(quán)訪問(wèn)Base類中被重載的m1()方法。但這并不意味著不可以用super.m1()的方法調(diào)用去使用這個(gè)方法。關(guān)系到derived2這個(gè)引用的變量,這個(gè)代碼是不合適的。Derived2的其他的操作映射同樣表明了每種類型操作的代碼執(zhí)行。
既然你有一個(gè)Derived2對(duì)象,可以用任何一個(gè)Derived2類型的變量去引用它。如圖1所示,Derived, Base和IType都是Derived2的基類。所以,Base類的引用是很有用的。圖3描述了以下語(yǔ)句的概念觀點(diǎn)。
Base base = derived2;
screen.width-600)this.style.width=screen.width-600;">
圖3:Base類引用附于Derived2對(duì)象之上
雖然Base類的引用不用再訪問(wèn)m3()和m4(),但是卻不會(huì)改變它Derived2對(duì)象的任何特征及操作映射。無(wú)論是變量derived2還是base,其調(diào)用m1()或m2(String)所執(zhí)行的代碼都是一樣的。
String tmp; // Derived2 reference (Figure 2) tmp = derived2.m1(); // tmp is "Derived.m1()" tmp = derived2.m2( "Hello" ); // tmp is "Derived2.m2( Hello )"
// Base reference (Figure 3)
tmp = base.m1(); // tmp is "Derived.m1()" tmp = base.m2( "Hello" ); // tmp is "Derived2.m2( Hello )"
|
兩個(gè)引用之所以調(diào)用同一個(gè)行為,是因?yàn)镈erived2對(duì)象并不知道去調(diào)用哪個(gè)方法。對(duì)象只知道什么時(shí)候調(diào)用,它隨著繼承實(shí)現(xiàn)的順序去執(zhí)行。這樣的順序決定了Derived2對(duì)象調(diào)用Derived里的m1()方法,并調(diào)用Derived2里的m2(String)方法。這種結(jié)果取決于對(duì)象本身的類型,而不是引用的類型。
盡管如此,但不意味著你用derived2和base引用的效果是完全一樣的。如圖3所示,Base的引用只能看到Base類型擁有的操作。所以,雖然Derived2有對(duì)方法m3()和m4()的映射,但是變量base不能訪問(wèn)這些方法。
String tmp; // Derived2 reference (Figure 2) tmp = derived2.m3(); // tmp is "Derived.m3()" tmp = derived2.m4(); // tmp is "Derived2.m4()"
// Base reference (Figure 3)
tmp = base.m3(); // Compile-time error tmp = base.m4(); // Compile-time error
|
運(yùn)行期的Derived2對(duì)象保持了接受m3()和m4()方法的能力。類型的限制使Base的引用不能在編譯期調(diào)用這些方法。編譯期的類型檢查像一套鎧甲,保證了運(yùn)行期對(duì)象只能和正確的操作進(jìn)行相互作用。換句話說(shuō),類型定義了對(duì)象間相互作用的邊界。
多態(tài)的依附性
類型的一致性是多態(tài)的核心。對(duì)象上的每一個(gè)引用,靜態(tài)的類型檢查器都要確認(rèn)這樣的依附和其對(duì)象的層次是一致的。當(dāng)一個(gè)引用成功的依附于另一個(gè)不同的對(duì)象時(shí),有趣的多態(tài)現(xiàn)象就產(chǎn)生了。(嚴(yán)格的說(shuō),對(duì)象類型是指類的定義。)你也可以把幾個(gè)不同的引用依附于同一個(gè)對(duì)象。在開(kāi)始更有趣的場(chǎng)景前,我們先來(lái)看一下下面的情況為什么不會(huì)產(chǎn)生多態(tài)。
多個(gè)引用依附于一個(gè)對(duì)象
圖2和圖3描述的例子是把兩個(gè)及兩個(gè)以上的引用依附于一個(gè)對(duì)象。雖然Derived2對(duì)象在被依附之后仍保持了變量的類型,但是,圖3中的Base類型的引用依附之后,其功能減少了。結(jié)論很明顯:把一個(gè)基類的引用依附于派生類的對(duì)象之上會(huì)減少其能力。
一個(gè)開(kāi)發(fā)這怎么會(huì)選擇減少對(duì)象能力的方案呢?這種選擇是間接的。假設(shè)有一個(gè)名為ref的引用依附于一個(gè)包含如下方法的類的對(duì)象:
public String poly1( Base base ) { return base.m1(); }
|
用一個(gè)Derived2的參數(shù)調(diào)用poly(Base)是符合參數(shù)類型檢查的:
方法調(diào)用把一個(gè)本地Base類型的變量依附在一個(gè)引入的對(duì)象上。所以,雖然這個(gè)方法只接受Base類型的參數(shù),但Derived2對(duì)象仍是允許的。開(kāi)發(fā)這就不必選擇丟失功能的方案。從人眼在通過(guò)Derived2對(duì)象時(shí)所看到的情況,Base類型引用的依附導(dǎo)致了功能的喪失。但從執(zhí)行的觀點(diǎn)看,每一個(gè)傳入poly1(Base)的參數(shù)都認(rèn)為是Base的對(duì)象。執(zhí)行機(jī)并不在乎有多個(gè)引用指向同一個(gè)對(duì)象,它只注重把指向另一個(gè)對(duì)象的引用傳給方法。這些對(duì)象的類型不一致并不是主要問(wèn)題。執(zhí)行器只關(guān)心給運(yùn)行時(shí)的對(duì)象找到適當(dāng)?shù)膶?shí)現(xiàn)。面向類型的觀點(diǎn)展示了多態(tài)的巨大能力。
附于多個(gè)對(duì)象的引用
讓我們來(lái)看一下發(fā)生在poly1(Base)中的多態(tài)行為。下面的代碼創(chuàng)建了三個(gè)對(duì)象,并通過(guò)引用傳給poly1(Base):
Derived2 derived2 = new Derived2(); Derived derived = new Derived(); Base base = new Base();
String tmp;
tmp = ref.poly1( derived2 ); // tmp is "Derived.m1()" tmp = ref.poly1( derived ); // tmp is "Derived.m1()" tmp = ref.poly1( base ); // tmp is "Base.m1()"
|
poly1(Base)的實(shí)現(xiàn)代碼是調(diào)用傳進(jìn)來(lái)的參數(shù)的m1()方法。圖3和圖4展示了把三個(gè)類的對(duì)象傳給方法時(shí),面向類型的所使用的體系結(jié)構(gòu)。
screen.width-600)this.style.width=screen.width-600;">
圖4:將Base引用指向Derived類,以及Base對(duì)象
請(qǐng)注意每個(gè)圖中方法m1()的映射。圖3中,m1()調(diào)用了Derived類的代碼;上面代碼中的注釋標(biāo)明了ploy1(Base)調(diào)用Derived.m1()。圖4中Derived對(duì)象調(diào)用的仍然是Derived類的m1()方法。最后,圖4中,Base對(duì)象調(diào)用的m1()是Base類中定義的代碼。
多態(tài)的魅力何在?再來(lái)看一下poly1(Base)的代碼,它可以接受任何屬于Base類范疇的參數(shù)。然而,當(dāng)他收到一個(gè)Derived2的對(duì)象時(shí),它實(shí)際上卻調(diào)用了Derived版本的方法。當(dāng)你根據(jù)Base類派生出其他類時(shí),如Derived,Derived2,poly1(Base)都可以接受這些參數(shù),并作出選擇調(diào)用合適的方法。多態(tài)允許你在完成poly1(Base)后擴(kuò)展它的用途。
這看起來(lái)當(dāng)然很神奇。基本的理解展示了多態(tài)的內(nèi)部工作原理。在面向類型的觀點(diǎn)中,底層的對(duì)象所實(shí)現(xiàn)的代碼是非實(shí)質(zhì)性的。重要的是,類型檢查器會(huì)在編譯期間為每個(gè)引用選擇合適的代碼以實(shí)現(xiàn)其方法。多態(tài)使開(kāi)發(fā)者運(yùn)用面向類型的觀點(diǎn),不考慮實(shí)現(xiàn)的細(xì)節(jié)。這樣有助于把類型和實(shí)現(xiàn)分離(實(shí)際用處是把接口和實(shí)現(xiàn)分離)。
對(duì)象接口
多態(tài)依賴于類型和實(shí)現(xiàn)的分離,多用來(lái)把接口和實(shí)現(xiàn)分離。但下面的觀點(diǎn)好像把Java的關(guān)鍵字interface搞得很糊涂。
更為重要的使開(kāi)發(fā)者們?cè)鯓永斫舛陶Z(yǔ)“the interface to an object",典型地,根據(jù)上下文,這個(gè)短語(yǔ)的意思是指一切對(duì)象類中所定義的方法,至一切對(duì)象公開(kāi)的方法。這種傾向于以實(shí)現(xiàn)為中心的觀點(diǎn)較之于面向類型的觀點(diǎn)來(lái)說(shuō),使我們更加注重于對(duì)象在運(yùn)行期的能力。圖3中,引用面板的對(duì)象表面被標(biāo)志成"Derived2 Object"。這個(gè)面板上列出了Derived2對(duì)象的所有可用的方法。但是要理解多態(tài),我們必須從實(shí)現(xiàn)這一層次上解放出來(lái),并注意面向類型的透視圖中被標(biāo)為"Base Reference"的面板。在這一層意思上,引用變量的類型指明了一個(gè)對(duì)象的表面。這只是一個(gè)表面,不是接口。在類型一致的原則下,我們可以用面向類型的觀點(diǎn),為一個(gè)對(duì)象依附多個(gè)引用。對(duì)interface to an object這個(gè)短語(yǔ)的理解沒(méi)有確定的理解。
在類型概念中,the interface to an object refers 引用了面向類型觀點(diǎn)的最大可能----如圖2的情形。把一個(gè)基類的引用指向相同的對(duì)象縮小了這樣的觀點(diǎn)----如圖3所示。類型概念能使人獲得把對(duì)象間的相互作用同實(shí)現(xiàn)細(xì)節(jié)分離的要領(lǐng)。相對(duì)于一個(gè)對(duì)象的接口,面向類型的觀點(diǎn)更鼓勵(lì)人們?nèi)ナ褂靡粋€(gè)對(duì)象的引用。引用類型規(guī)定了對(duì)象間的相互作用。當(dāng)你考慮一個(gè)對(duì)象能做什么的時(shí)候,只需搞明白他的類型,而不需要去考慮他的實(shí)現(xiàn)細(xì)節(jié)。
Java接口
以上所談到的多態(tài)行為用到了類的繼承關(guān)系所建立起來(lái)的子類型關(guān)系。Java接口同樣支持用戶定義的類型,相對(duì)地,Java的接口機(jī)制啟動(dòng)了建立在類型層次結(jié)構(gòu)上的多態(tài)行為。假設(shè)一個(gè)名為ref的引用變量,并使其指向一個(gè)包含一下方法的類對(duì)象:
public String poly2( IType iType ) { return iType.m3(); }
|
為了弄明白poly2(IType)中的多態(tài),以下的代碼從不同的類創(chuàng)建兩個(gè)對(duì)象,并分別把他們傳給poly2(IType):
Derived2 derived2 = new Derived2(); Separate separate = new Separate();
String tmp;
tmp = ref.poly2( derived2 ); // tmp is "Derived.m3()" tmp = ref.poly2( separate ); // tmp is "Separate.m3()"
|
上面的代碼類似于關(guān)于poly1(Base)中的多態(tài)的討論。poly2(IType)的實(shí)現(xiàn)代碼是調(diào)用每個(gè)對(duì)象的本地版本的m3()方法。如同以前,代碼的注釋表明了每次調(diào)用所返回的CString類型的結(jié)果。圖5表明了兩次調(diào)用poly2(IType)的概念結(jié)構(gòu):

圖5:指向Derived2和Separate對(duì)象的IType引用
方法poly1(Base)和poly2(IType)中所表現(xiàn)的多態(tài)行為的相似之處可以從透視圖中直接看出來(lái)。把我們?cè)趯?shí)現(xiàn)在一層上的理解再提高一層,就可以看到這兩段代碼的技巧。基類的引用指向了作為參數(shù)傳進(jìn)的類,并且按照類型的限制調(diào)用對(duì)象的方法。引用既不知道也不關(guān)心執(zhí)行哪一段代碼。編譯期間的子類型關(guān)系檢查保證了通過(guò)的對(duì)象有能力在被調(diào)用的時(shí)候選擇合適的實(shí)現(xiàn)代碼。
然而,他們?cè)趯?shí)現(xiàn)層上有一個(gè)重要的差別。在poly1(Base)的例子中(圖3和圖4),Base-Derived-Derived2的類繼承結(jié)構(gòu)為子類型關(guān)系的建立提供了條件,并決定了方法去調(diào)用哪段代碼。在poly2(IType)的例子中(如圖5),則是完全不同的動(dòng)態(tài)發(fā)生的。Derived2和Separate不共享任何實(shí)現(xiàn)的層次,但是他們還是通過(guò)IType的引用展示了多態(tài)的行為。
這樣的多態(tài)行為使Java的接口的功能的重大意義顯得很明顯。圖1中的UML類圖說(shuō)明了Derived是Base和IType的子類型。通過(guò)完全脫離實(shí)現(xiàn)細(xì)節(jié)的類型的定義方法,Java實(shí)現(xiàn)了多類型繼承,并且不存在Java所禁止的多繼承所帶來(lái)的煩人的問(wèn)題。完全脫離實(shí)現(xiàn)層次的類可以按照J(rèn)ava接口實(shí)現(xiàn)分組。在圖1中,接口IType和Derived,Separate以及這類型的其他子類型應(yīng)該劃為一組。
按照這種完全不同于實(shí)現(xiàn)層次的分類方法,Java的接口機(jī)制是多態(tài)變得很方便,哪怕不存在任何共享的實(shí)現(xiàn)或者復(fù)寫的方法。如圖5所示,一個(gè)IType的引用,用多態(tài)的方法訪問(wèn)到了Derived2和Separate對(duì)象的m3()方法。
再次探討對(duì)象的接口
注意圖5中的Derived2和Separate對(duì)象的對(duì)m1()的映射方法。如前所述,每一個(gè)對(duì)象的接口都包含方法m1()。但卻沒(méi)有辦法用這兩個(gè)對(duì)象使方法m1()表現(xiàn)出多態(tài)的行為。每一個(gè)對(duì)象占有一個(gè)m1()方法是不夠的。必須存在一個(gè)可以操作m1()方法的類型,通過(guò)這個(gè)類型可以看到對(duì)象。這些對(duì)象似乎是共享了m1()方法,但在沒(méi)有共同基類的條件下,多態(tài)是不可能的。通過(guò)對(duì)象的接口來(lái)看多態(tài),會(huì)把這個(gè)概念搞混。
結(jié)論
從全文所述的面向?qū)ο蠖鄳B(tài)所建立起來(lái)的子類型多態(tài),你可以清楚地認(rèn)識(shí)到這種面向類型的觀點(diǎn)。如果你想理解子類型多態(tài)的思想,就應(yīng)該把注意力從實(shí)現(xiàn)的細(xì)節(jié)轉(zhuǎn)移到類型的上。類型把對(duì)象分成組,并且管理著這些對(duì)象的接口。類型的繼承層次結(jié)構(gòu)決定了實(shí)現(xiàn)多態(tài)所需的類型關(guān)系。
有趣的是,實(shí)現(xiàn)的細(xì)節(jié)并不影響子類型多態(tài)的層次結(jié)構(gòu)。類型決定了對(duì)象調(diào)用什么方法,而實(shí)現(xiàn)則決定了對(duì)象怎么執(zhí)行這個(gè)方法。也就是說(shuō),類型表明了責(zé)任,而負(fù)責(zé)實(shí)施的則是具體的實(shí)現(xiàn)。將實(shí)現(xiàn)和類型分離后,我們好像看到了這兩個(gè)部分在一起跳舞,類型決定了他的舞伴和舞蹈的名字,而實(shí)現(xiàn)則是舞蹈動(dòng)作的設(shè)計(jì)師。
J2EE學(xué)習(xí)者越來(lái)越多,J2EE本身技術(shù)不斷在發(fā)展,涌現(xiàn)出各種概念,本文章試圖從一種容易理解的角度對(duì)這些概念向初學(xué)者進(jìn)行解釋,以便掌握學(xué)習(xí)J2EE學(xué)習(xí)方向。首先我們需要知道Java和J2EE是兩個(gè)不同概念,Java不只是指一種語(yǔ)言,已經(jīng)代表與微軟不同的另外一個(gè)巨大陣營(yíng),所以Java有時(shí)是指一種軟件系統(tǒng)的流派,當(dāng)然目前主要是.NET和Java兩大主流體系。
J2EE可以說(shuō)指Java在數(shù)據(jù)庫(kù)信息系統(tǒng)上實(shí)現(xiàn),數(shù)據(jù)庫(kù)信息系統(tǒng)從早期的dBase、到Delphi/VB等C/S結(jié)構(gòu),發(fā)展到B/S(Browser瀏覽器/Server服務(wù)器)結(jié)構(gòu),而J2EE主要是指B/S結(jié)構(gòu)的實(shí)現(xiàn)。
J2EE又是一種框架和標(biāo)準(zhǔn),框架類似API、庫(kù)的概念,但是要超出它們。如果需要詳細(xì)了解框架,可先從設(shè)計(jì)模式開(kāi)始學(xué)習(xí)。
J2EE是一個(gè)虛的大的概念,J2EE標(biāo)準(zhǔn)主要有三種子技術(shù)標(biāo)準(zhǔn):WEB技術(shù)、EJB技術(shù)和JMS,談到J2EE應(yīng)該說(shuō)最終要落實(shí)到這三個(gè)子概念上。
這三種技術(shù)的每個(gè)技術(shù)在應(yīng)用時(shí)都涉及兩個(gè)部分:容器部分和應(yīng)用部分,Web容器也是指Jsp/Servlet容器,你如果要開(kāi)發(fā)一個(gè)Web應(yīng)用,無(wú)論是編譯或運(yùn)行,都必須要有Jsp/Servlet庫(kù)或API支持(除了JDK/J2SE以外)。
Web技術(shù)中除了Jsp/Servlet技術(shù)外,還需要JavaBeans或Java Class實(shí)現(xiàn)一些功能或者包裝攜帶數(shù)據(jù),所以Web技術(shù)最初裸體簡(jiǎn)稱為Jsp/Servlet+JavaBeans系統(tǒng)。
談到JavaBeans技術(shù),就涉及到組件構(gòu)件技術(shù)(component),這是Java的核心基礎(chǔ)部分,很多軟件設(shè)計(jì)概念(設(shè)計(jì)模式)都是通過(guò)JavaBeans實(shí)現(xiàn)的。
JavaBeans不屬于J2EE概念范疇中,如果一個(gè)JavaBeans對(duì)象被Web技術(shù)(也就是Jsp/Servlet)調(diào)用,那么JavaBeans就運(yùn)行在J2EE的Web容器中;如果它被EJB調(diào)用,它就運(yùn)行在EJB容器中。
EJB(企業(yè)JavaBeans)是普通JavaBeans的一種提升和規(guī)范,因?yàn)槠髽I(yè)信息系統(tǒng)開(kāi)發(fā)中需要一個(gè)可伸縮的性能和事務(wù)、安全機(jī)制,這樣能保證企業(yè)系統(tǒng)平滑發(fā)展,而不是發(fā)展到一種規(guī)模重新更換一套軟件系統(tǒng)。
至此,JavaBeans組件發(fā)展到EJB后,并不是說(shuō)以前的那種JavaBeans形式就消失了,這就自然形成了兩種JavaBeans技術(shù):EJB和POJO,POJO完全不同于EJB概念,指的是普通JavaBeans,而且這個(gè)JavaBeans不依附某種框架,或者干脆可以說(shuō):這個(gè)JavaBeans是你為這個(gè)應(yīng)用程序單獨(dú)開(kāi)發(fā)創(chuàng)建的。
J2EE應(yīng)用系統(tǒng)開(kāi)發(fā)工具有很多:如JBuilder、Eclipse等,這些IDE首先是Java開(kāi)發(fā)工具,也就是說(shuō),它們首要基本功能是可以開(kāi)發(fā)出JavaBeans或Java class,但是如果要開(kāi)發(fā)出J2EE系統(tǒng),就要落實(shí)到要么是Web技術(shù)或EJB技術(shù),那么就有可能要一些專門模塊功能(如eclipse需要lomboz插件),最重要的是,因?yàn)镴2EE系統(tǒng)區(qū)分為容器和應(yīng)用兩個(gè)部分,所以,在任何開(kāi)發(fā)工具中開(kāi)發(fā)J2EE都需要指定J2EE容器。
J2EE容器分為WEB容器和EJB容器,Tomcat/Resin是Web容器;JBoss是EJB容器+Web容器等,其中Web容器直接使用Tomcat實(shí)現(xiàn)的。所以你開(kāi)發(fā)的Web應(yīng)用程序可以在上面兩種容器運(yùn)行,而你開(kāi)發(fā)的Web+EJB應(yīng)用則只可以在JBoss服務(wù)器上運(yùn)行,商業(yè)產(chǎn)品Websphere/Weblogic等和JBoss屬于同一種性質(zhì)。
J2EE容器也稱為J2EE服務(wù)器,大部分時(shí)它們概念是一致的。
如果你的J2EE應(yīng)用系統(tǒng)的數(shù)據(jù)庫(kù)連接是通過(guò)JNDI獲得,也就是說(shuō)是從容器中獲得,那么你的J2EE應(yīng)用系統(tǒng)基本與數(shù)據(jù)庫(kù)無(wú)關(guān),如果你在你的J2EE應(yīng)用系統(tǒng)耦合了數(shù)據(jù)庫(kù)JDBC驅(qū)動(dòng)的配置,那么你的J2EE應(yīng)用系統(tǒng)就有數(shù)據(jù)庫(kù)概念色彩,作為一個(gè)成熟需要推廣的J2EE應(yīng)用系統(tǒng),不推薦和具體數(shù)據(jù)庫(kù)耦合,當(dāng)然這其中如何保證J2EE應(yīng)用系統(tǒng)運(yùn)行性能又是體現(xiàn)你的設(shè)計(jì)水平了。
衡量J2EE應(yīng)用系統(tǒng)設(shè)計(jì)開(kāi)發(fā)水平高低的標(biāo)準(zhǔn)就是:解耦性;你的應(yīng)用系統(tǒng)各個(gè)功能是否能夠徹底脫離?是否不相互依賴,也只有這樣,才能體現(xiàn)可維護(hù)性、可拓展性的軟件設(shè)計(jì)目標(biāo)。
為了達(dá)到這個(gè)目的,誕生各種框架概念,J2EE框架標(biāo)準(zhǔn)將一個(gè)系統(tǒng)劃分為WEB和EJB主要部分,當(dāng)然我們有時(shí)不是以這個(gè)具體技術(shù)區(qū)分,而是從設(shè)計(jì)上抽象為表現(xiàn)層、服務(wù)層和持久層,這三個(gè)層次從一個(gè)高度將J2EE分離開(kāi)來(lái),實(shí)現(xiàn)解耦目的。
因此,我們實(shí)際編程中,也要將自己的功能向這三個(gè)層次上靠,做到大方向清楚,涇渭分明,但是沒(méi)有技術(shù)上約束限制要做到這點(diǎn)是很不容易的,因此我們還是必須借助J2EE具體技術(shù)來(lái)實(shí)現(xiàn),這時(shí),你可以使用EJB規(guī)范實(shí)現(xiàn)服務(wù)層和持久層,Web技術(shù)實(shí)現(xiàn)表現(xiàn)層;
EJB為什么能將服務(wù)層從Jsp/Servlet手中分離出來(lái),因?yàn)樗鼘?duì)JavaBeans編碼有強(qiáng)制的約束,現(xiàn)在有一種對(duì)JavaBeans弱約束,使用Ioc模式實(shí)現(xiàn)的(當(dāng)然EJB 3.0也采取這種方式),在Ioc模式誕生前,一般都是通過(guò)工廠模式來(lái)對(duì)JavaBeans約束,形成一個(gè)服務(wù)層,這也是是Jive這樣開(kāi)源論壇設(shè)計(jì)原理之一。
由此,將服務(wù)層從表現(xiàn)層中分離出來(lái)目前有兩種可選架構(gòu)選擇:管理普通JavaBeans(POJO)框架(如Spring、JdonFramework)以及管理EJB的EJB框架,因?yàn)镋JB不只是框架,還是標(biāo)準(zhǔn),而標(biāo)準(zhǔn)可以擴(kuò)展發(fā)展,所以,這兩種區(qū)別將來(lái)是可能模糊,被納入同一個(gè)標(biāo)準(zhǔn)了。 但是,個(gè)人認(rèn)為:標(biāo)準(zhǔn)制定是為某個(gè)目的服務(wù)的,總要犧牲一些換取另外一些,所以,這兩種架構(gòu)會(huì)長(zhǎng)時(shí)間并存。
這兩種架構(gòu)分歧也曾經(jīng)誕生一個(gè)新名詞:完全POJO的系統(tǒng)也稱為輕量級(jí)系統(tǒng)(lightweight),其實(shí)這個(gè)名詞本身就沒(méi)有一個(gè)嚴(yán)格定義,更多是一個(gè)吸引人的招牌,輕量是指容易學(xué)習(xí)容易使用嗎?按照這個(gè)定義,其實(shí)輕量Spring等系統(tǒng)并不容易學(xué)習(xí);而且EJB 3.0(依然叫EJB)以后的系統(tǒng)是否可稱為輕量級(jí)了呢?
前面談了服務(wù)層框架,使用服務(wù)層框架可以將JavaBeans從Jsp/Servlet中分離出來(lái),而使用表現(xiàn)層框架則可以將Jsp中剩余的JavaBeans完全分離,這部分JavaBeans主要負(fù)責(zé)顯示相關(guān),一般是通過(guò)標(biāo)簽庫(kù)(taglib)實(shí)現(xiàn),不同框架有不同自己的標(biāo)簽庫(kù),Struts是應(yīng)用比較廣泛的一種表現(xiàn)層框架。
這樣,表現(xiàn)層和服務(wù)層的分離是通過(guò)兩種框架達(dá)到目的,剩余的就是持久層框架了,通過(guò)持久層的框架將數(shù)據(jù)庫(kù)存儲(chǔ)從服務(wù)層中分離出來(lái)是其目的,持久層框架有兩種方向:直接自己編寫JDBC等SQL語(yǔ)句(如iBatis);使用O/R Mapping技術(shù)實(shí)現(xiàn)的Hibernate和JDO技術(shù);當(dāng)然還有EJB中的實(shí)體Bean技術(shù)。
持久層框架目前呈現(xiàn)百花齊放,各有優(yōu)缺點(diǎn)的現(xiàn)狀,所以正如表現(xiàn)層框架一樣,目前沒(méi)有一個(gè)框架被指定為標(biāo)準(zhǔn)框架,當(dāng)然,表現(xiàn)層框架現(xiàn)在又出來(lái)了一個(gè)JSF,它代表的頁(yè)面組件概念是一個(gè)新的發(fā)展方向,但是復(fù)雜的實(shí)現(xiàn)讓人有些忘而卻步。
在所有這些J2EE技術(shù)中,雖然SUN公司發(fā)揮了很大的作用,不過(guò)總體來(lái)說(shuō):網(wǎng)絡(luò)上有這樣一個(gè)評(píng)價(jià):SUN的理論天下無(wú)敵;SUN的產(chǎn)品用起來(lái)撞墻;對(duì)于初學(xué)者,特別是那些試圖通過(guò)或已經(jīng)通過(guò)SUN認(rèn)證的初學(xué)者,趕快擺脫SUN的陰影,立即開(kāi)溜,使用開(kāi)源領(lǐng)域的產(chǎn)品來(lái)實(shí)現(xiàn)自己的應(yīng)用系統(tǒng)。
最后,你的J2EE應(yīng)用系統(tǒng)如果采取上面提到的表現(xiàn)層、服務(wù)層和持久層的框架實(shí)現(xiàn),基本你也可以在無(wú)需深刻掌握設(shè)計(jì)模式的情況下開(kāi)發(fā)出一個(gè)高質(zhì)量的應(yīng)用系統(tǒng)了。
還要注意的是: 開(kāi)發(fā)出一個(gè)高質(zhì)量的J2EE系統(tǒng)還需要正確的業(yè)務(wù)需求理解,那么域建模提供了一種比較切實(shí)可行的正確理解業(yè)務(wù)需求的方法,相關(guān)詳細(xì)知識(shí)可從UML角度結(jié)合理解。
當(dāng)然,如果你想設(shè)計(jì)自己的行業(yè)框架,那么第一步從設(shè)計(jì)模式開(kāi)始吧,因?yàn)樵O(shè)計(jì)模式提供你一個(gè)實(shí)現(xiàn)JavaBeans或類之間解耦參考實(shí)現(xiàn)方法,當(dāng)你學(xué)會(huì)了系統(tǒng)基本單元JavaBean或類之間解耦時(shí),那么系統(tǒng)模塊之間的解耦你就可能掌握,進(jìn)而你就可以實(shí)現(xiàn)行業(yè)框架的提煉了,這又是另外一個(gè)發(fā)展方向了。
public class statictest {
??? public statictest()
??? {???
??? }
??? public static void prin(String s)
??? {
??? System.out.println(s);
??? }
??? public static int i=printy("hehe");
??? public static int printy(String s)
????? {
????????? System.out.println(s);
????????? return 4;
?????? }
??? public static void main(String[] args) {
??????? statictest.prin("fdsafa");
??? }
}
輸出結(jié)果 hehe fdsafa
當(dāng)生成一個(gè)類的對(duì)象時(shí),或者首次訪問(wèn)屬于哪個(gè)類的靜態(tài)數(shù)據(jù)成員時(shí),,進(jìn)行初始化.
package untitled4;
class teststatic
{
?? static int i=prin("test");
?? static int prin(String s)
?? {
?????? System.out.println(s);
?????? return 2;
?? }
?? static void play()
?? {
?? System.out.println("play");
?? }
}
public class statictest2 {
??? public statictest2() {
??? }
??? public static void main(String[] args) {
????? teststatic.play();
??? }
}
輸出結(jié)果?? TEST PLAY
對(duì)于類CLASS A的執(zhí)行相當(dāng)于調(diào)用A.main(),,他首先對(duì)A的元素初始化(遵循,從A的基類開(kāi)始,STATIC)
非STATIC在對(duì)象生成時(shí)候才初始化