在 多型與 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
是Swimme
r型態,所以編譯器會問,實作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
行為,你就都不用撰寫新的方法,可維護性顯然提高許多!