記得繼承了什麼? 中,SwordsMan與Magician都繼承了Sprite,要建立SwordsMan與Magician,並宣告變數來參考它們,可以這麼寫:
SwordsMan swordsMan = new SwordsMan();
Magician magician = new Magician();
Magician magician = new Magician();
這是最基本的,而在 千面人 中,你知道,運用「是一種」的關係,你還可以這麼寫:
Sprite sprite1 = new SwordsMan();
Sprite sprite2 = new Magician();
Sprite sprite2 = new Magician();
這沒有問題,因為SwordsMan是一種Sprite,而Magician是一種Sprite,程式的讀法可從右往左,判別方式就是「右邊的東西是不是一種左邊的東西」。
你可以將自己當作編譯器,檢查程式邏輯,例如:
Sprite sprite1 = new SwordsMan();
沒問題,這會編譯成功,因為從右往左讀,SwordsMan是一種Sprite。但如果:
SwordsMan swordsMan = new Sprite();
就會編譯失敗,如果你是編譯器,你會抱怨,Sprite不一定是種SwordsMan,所以就不想編譯。下面這個也是:
Sprite sprite1 = new SwordsMan();
SwordsMan swordsMan = sprite1;
SwordsMan swordsMan = sprite1;
第一行編譯沒問題,第二行呢?Java是靜態語言,變數宣告時會帶有型態,就編譯器的角度來說,在看到第二行時,它發現sprite1是Sprite型態,那麼由右往左看,Sprite是一種SwordsMan嗎?不一定!所以就不想編譯通過。除非你這麼寫:
Sprite sprite1 = new SwordsMan();
SwordsMan swordsMan = (SwordsMan) sprite1;
SwordsMan swordsMan = (SwordsMan) sprite1;
一般會說,第二行作了轉型(Cast),但白話的說法是,編譯器原本 很囉嗦,告訴你Sprite不一定是種SwordsMan,但你要編譯器住嘴,所以在前面加上了(SwordsMain),這相當於告訴編譯器:「我知道 啦!Sprite不一定是種SwordsMan,不過我已經用(SwordsMan)告訴你,它就是SwordsMan,你就別再囉嗦了!」,編譯器於是:「好吧!那我就編譯囉!後果你自己負責!」
就上例而言,執行時也不會有問題,但是如果是下面這個:
Sprite sprite1 = new Magician();
SwordsMan swordsMan = (SwordsMan) sprite1;
SwordsMan swordsMan = (SwordsMan) sprite1;
這會編譯成功,在第二行時,編譯器其實會發現,Sprite不一定是SwordsMan,但也發現,你加了(SwordsMan),所以這表示你自己後果 自負,所以就通過編譯,因此沒有編譯時期錯誤訊息。但事實上,sprite1明明就參考至Magician實例,你硬要將魔法師轉職為 SwordsMan,後果就是ClassCastException,這是執行時期錯誤。
所謂的後果自負,就是執行時期出搥,這可不是編譯器的錯…XD
在Java中,轉型的概念基本上就是如此,只是要編譯器住嘴。
你可以多作一些練習:
SwordsMan swordsMan = new SwordsMan();
Sprite sprite = swordsMan;
Sprite sprite = swordsMan;
這會編譯成功,記得,對判斷是不是「是一種」關係,編譯器每次只看一行:
SwordsMan swordsMan = new SwordsMan();
Sprite sprite = swordsMan;
SwordsMan swordsMan = sprite;
Sprite sprite = swordsMan;
SwordsMan swordsMan = sprite;
上面第三行會編譯失敗,因為就第三行來看,sprite是Sprite型態的變數,Sprite不一定是種SwordsMan,所以編譯失敗是必然的。除非你叫編譯器別囉嗦:
SwordsMan swordsMan = new SwordsMan();
Sprite sprite = swordsMan;
SwordsMan swordsMan = (SwordsMan) sprite;
Sprite sprite = swordsMan;
SwordsMan swordsMan = (SwordsMan) sprite;
上例編譯成功,執行也不會有錯誤,因為sprite實際上還是參考至SwordsMan實例。但下面這個就會執行時期錯誤:
SwordsMan swordsMan = new SwordsMan();
Sprite sprite = swordsMan;
Magician magician = (Magician) sprite;
Sprite sprite = swordsMan;
Magician magician = (Magician) sprite;
編譯可以成功,但實際上,sprite是參考至SwordsMan,執行時間你要它轉型為Magician,就會丟出ClassCastException。
要不要轉型,就看你想不想讓編譯器住嘴,一旦要它住嘴,那麼你就要自己確定執行時間沒有錯誤。
所以在 Promotion 與 Cast 中,是否要轉型,也是可以如此判斷。例如:
float PI = 3.14;
編譯器會抱怨,3.14是double型態,怎麼可以裝入float呢?如果你明確要它住嘴,那麼就可以這麼寫:
float PI = (float) 3.14;
後果呢?就是如果精度被裁掉了,那編譯器可不管了!