在 多型與 is-a 關係 曾試著當編譯器,判斷哪些繼承多型語法可以通過編譯,加入扮演(Cast)語法的目的又是為何,以及哪些情況下,執行時期會扮演失敗,哪些又可扮演成功。會使用介面定義行為之後,也要再來當編譯器,看看哪些是合法的多型語法。例如:
Swimmer swimmer1 = new Shark();
Swimmer swimmer2 = new Human();
Swimmer swimmer3 = new Submarine();
這三行程式碼都可以通過編譯,判斷方式是「右邊是不是擁有左邊的行為」,或「是右邊物件是不是實作了左邊介面」。
Shark擁有Swimmer行為嗎?有的!因為Fish實作了Swimmer介面,也就是Fish擁有Swimmer行為,Shark繼承Fish,當然也擁有Swimmer行為,所以通過編譯,Human與Submarine也都實作了Swimmer介面,所以通過編譯。更進一步地,來看看底下的程式碼是否可通過編譯?
Swimmer swimmer = new Shark();Shark shark = swimmer;第一行要判斷
Shark是否擁有Swimmer行為?是的!可通過編譯,但第二行呢?swimmer是Swimmer型態,編譯器看到該行會想到,有Swimmer行為的物件是不是Shark呢?這可不一定!也許實際上是Human實例!因為有Swimmer行為的物件不一定是Shark,所以第二行編譯失敗!就上面的程式碼片段而言,實際上
swimmer是參考至Shark實例,你可以加上扮演(Cast)語法:Swimmer swimmer = new Shark();Shark shark = (Shark) swimmer;對第二行的語意而言,就是在告訴編譯器,對!你知道有
Swimmer行為的物件,不一定是Shark,不過你就是要它扮演Shark,所以編譯器就別再囉嗦了。可以通過編譯,執行時期swimmer確實也是參考Shark實例,所以也沒有錯誤。底下的程式片段會在第二行編譯失敗:
Swimmer swimmer = new Shark();Fish fish = swimmer;第二行
swimmer是Swimmer型態,所以編譯器會問,實作Swimmer介面的物件是不是繼承Fish?不一定,也許是Submarine!因為會實作Swimmer介面的並不一定繼承Fish,所以編譯失敗了。如果加上扮演語法:Swimmer swimmer = new Shark();Fish fish = (Fish) swimmer;第二行告訴編譯器,你知道有
Swimmer行為的物件,不一定繼承Fish,不過你就是要它扮演Fish,所以編譯器就別再囉嗦了。可以通過編譯,執行時期swimmer確實也是參考Shark實例,它是一種Fish,所以也沒有錯誤。下面這個例子就會拋出
ClassCastException錯誤:Swimmer swimmer = new Human();Shark shark = (Shark) swimmer;在第二行,
swimmer實際上參考了Human實例,你要他扮演鯊魚?這太荒繆了!所以執行時就出錯了。類似地,底下的例子也會出錯:Swimmer swimmer = new Submarine();Fish fish = (Fish) swimmer;在第二行,
swimmer實際上參考了Submarine實例,你要他扮演魚?又不是在演哆啦A夢中海底鬼岩城,哪來的機器魚情節!Submarine不是一種Fish,執行時期會因為扮演失敗而拋出ClassCastException。知道以下的語法,哪些可以通過編譯,哪些可以扮演成功作什麼?來考慮一個需求,寫個
static的swim()方法,讓會游的東西都游起來,在不會使用介面多型語法時,也許你會寫下:public static void doSwim(Fish fish) { fish.swim();}public static void doSwim(Human human) { human.swim();}public static void doSwim(Submarine submarine) { submarine.swim();}老實說,如果已經會寫下接收
Fish的doSwim()版本,程式觀念還算是不錯的,因為至少你知道,只要有繼承Fish,無論是Anemonefish、Shark或Piranha,都可以使用Fish的doSwim()版本,至少你會使用繼承時的多型,至於Human與Submarine各使用其接收Human及Submarine的doSwim()版本。問題是,如果「種類」很多怎麼辦?多了水母、海蛇、蟲等種類呢?每個種類重載一個方法出來嗎?其實在你的設計中,會游泳的東西,都擁有
Swimmer的行為,都實作了Swimmer介面,所以你只要這麼設計就可以了:package cc.openhome;
public class Ocean {
public static void main(String[] args) {
doSwim(new Anemonefish("尼莫"));
doSwim(new Shark("蘭尼"));
doSwim(new Human("賈斯汀"));
doSwim(new Submarine("黃色一號"));
}
static void doSwim(Swimmer swimmer) {
swimmer.swim();
}
}
執行結果如下:
小丑魚 尼莫 游泳
鯊魚 蘭尼 游泳
人類 賈斯汀 游泳
潛水艇 黃色一號 潛行
鯊魚 蘭尼 游泳
人類 賈斯汀 游泳
潛水艇 黃色一號 潛行
只要是實作
Swimmer介面的物件,都可以使用範例中的doSwim()方法,Anemonefish、Shark、Human、Submarine等,都實作了Swimmer介面,再多種類,只要物件擁有Swimmer行為,你就都不用撰寫新的方法,可維護性顯然提高許多!

