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從概念的觀點描述了Java指令。
Derived2 derived2 = new Derived2();
圖2 :Derived2 對象上的引用
上文中聲明了derived2這個對象,它是Derived2類的。圖2種的最頂層把Derived2引用描述成一個集合的窗口,雖然其下的Derived2對象是可見的。這裏為每個Derived2類型的操作留了一個孔。Derived2對象的每個操作都去映射適當的代碼,按照上麵的代碼所描述的那樣。例如,Derived2對象映射了在Derived中定義的m1()方法。而且還重載了Base類的m1()方法。一個Derived2的引用變量無權訪問Base類中被重載的m1()方法。但這並不意味著不可以用super.m1()的方法調用去使用這個方法。關係到derived2這個引用的變量,這個代碼是不合適的。Derived2的其他的操作映射同樣表明了每種類型操作的代碼執行。
既然你有一個Derived2對象,可以用任何一個Derived2類型的變量去引用它。如圖1所示,Derived, Base和IType都是Derived2的基類。所以,Base類的引用是很有用的。圖3描述了以下語句的概念觀點。
Base base = derived2;
圖3:Base類引用附於Derived2對象之上
雖然Base類的引用不用再訪問m3()和m4(),但是卻不會改變它Derived2對象的任何特征及操作映射。無論是變量derived2還是base,其調用m1()或m2(String)所執行的代碼都是一樣的。
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 )"
兩個引用之所以調用同一個行為,是因為Derived2對象並不知道去調用哪個方法。對象隻知道什麼時候調用,它隨著繼承實現的順序去執行。這樣的順序決定了Derived2對象調用Derived裏的m1()方法,並調用Derived2裏的m2(String)方法。這種結果取決於對象本身的類型,而不是引用的類型。
盡管如此,但不意味著你用derived2和base引用的效果是完全一樣的。如圖3所示,Base的引用隻能看到Base類型擁有的操作。所以,雖然Derived2有對方法m3()和m4()的映射,但是變量base不能訪問這些方法。
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
運行期的Derived2對象保持了接受m3()和m4()方法的能力。類型的限製使Base的引用不能在編譯期調用這些方法。編譯期的類型檢查像一套鎧甲,保證了運行期對象隻能和正確的操作進行相互作用。換句話說,類型定義了對象間相互作用的邊界。
多態的依附性
類型的一致性是多態的核心。對象上的每一個引用,靜態的類型檢查器都要確認這樣的依附和其對象的層次是一致的。當一個引用成功的依附於另一個不同的對象時,有趣的多態現象就產生了。(嚴格的說,對象類型是指類的定義。)你也可以把幾個不同的引用依附於同一個對象。在開始更有趣的場景前,我們先來看一下下麵的情況為什麼不會產生多態。
多個引用依附於一個對象
圖2和圖3描述的例子是把兩個及兩個以上的引用依附於一個對象。雖然Derived2對象在被依附之後仍保持了變量的類型,但是,圖3中的Base類型的引用依附之後,其功能減少了。結論很明顯:把一個基類的引用依附於派生類的對象之上會減少其能力。
一個開發這怎麼會選擇減少對象能力的方案呢?這種選擇是間接的。假設有一個名為ref的引用依附於一個包含如下方法的類的對象:
public String poly1( Base base )
{
return base.m1();
}
用一個Derived2的參數調用poly(Base)是符合參數類型檢查的:
ref.poly1( derived2 );
方法調用把一個本地Base類型的變量依附在一個引入的對象上。所以,雖然這個方法隻接受Base類型的參數,但Derived2對象仍是允許的。開發這就不必選擇丟失功能的方案。從人眼在通過Derived2對象時所看到的情況,Base類型引用的依附導致了功能的喪失。但從執行的觀點看,每一個傳入poly1(Base)的參數都認為是Base的對象。執行機並不在乎有多個引用指向同一個對象,它隻注重把指向另一個對象的引用傳給方法。這些對象的類型不一致並不是主要問題。執行器隻關心給運行時的對象找到適當的實現。麵向類型的觀點展示了多態的巨大能力。
附於多個對象的引用
讓我們來看一下發生在poly1(Base)中的多態行為。下麵的代碼創建了三個對象,並通過引用傳給poly1(Base):
Derived2 derived2 = new Derived2();
Derived derived = new Derived();