Java一個很迷人的特性就是支援運行時多態(Polymophism). 這個特性省卻了許多維護類型的煩惱。你可以使用一個父類的引用(reference)指向一個子類,然後在運行時調用子類的方法。這樣無論以後你擴充了多少子類,子類的子類,都可以不更改任何代碼使得程式繼續運行,JVM在運行時會照顧一切。 為了說明這一點,請看下面例子:
example 1:
public class Animal {
public void move() {
System.out.println("Animal: generic move");
}
}
public class Horse extends Animal {
public void move() {
System.out.println("Horse: run");
}
public void jump() {
System.out.println("Horse: jump");
}
}
public class TestAnimals {
public static void move(Animal a) {
a.move();
}
public static void main(String[] args) {
Animal[] animals = {new Animal(), new Horse(), new Animal()};
for (Animal a:animals) {
move(a);
}
}
}
當我們運行UseAnimal的時候,結果如下:
Animal: generic move
Horse: run
如上所述,這樣做的一個好處是,我們只需要在UseAnimals中定義一個move(Animal a)方法,就可以對所有Animal進行操作, 無須理會具體的類型。如果我們animals數組中增加一個Dog類型,我們一樣可以調用 move方法,只要Dog類重寫了Animal中的move方法。
讓我們回到類型轉換的話題上來。在UseAnimals的main方法中. 在Java中,類及其子類的關心可以形象的看成一棵樹,其中父類在上,子類在下。向上裁剪顧名思義就是把一個子類裁剪為父類,這樣做的結果就是子類將“丟失”部分成員,而只能使用和父類共同擁有的成員,更準確的說是共有的API.
比如一下代碼是不能通過編譯的:
Animal animal = new Horse(); //upcasting
animal.jump(); //compiler complains, it cannot see "jump()"
在Java中,向上裁剪總是允許的,不需要強制指定轉換類型。 道理顯而易見,把一個較寬的類型轉換為一個交窄的類型總是可以的! 與upcasting 相對應的是downcasting。在上述例子中,Horse定義了一個jump方法。如果我們確實知道我們處理的是一個Horse對象,想調用它的jump方法,該怎樣做?
請看一下代碼:
public class TestAnimals {
public static void move(Animal a) {
a.move();
}
public static void main(String[] args) {
Animal[] animals = {new Animal(), new Horse(), new Animal()};
for (Animal a:animals) {
move(a);
if (a instanceof Horse) {
Horse h = (Horse)a;
h.jump();
}
}
}
}
為了實現這個目的,我們必須顯示的把一個Animal的引用轉化為一個Horse的引用,而且我們必須清楚的知道,我們要轉化的對象確實是一個Horse對象,這就是為什麼要使用instanceof的原因了。如果沒有instanceof這個判斷,會怎麼樣?
請看修改過的代碼:
public class TestAnimals {
public static void move(Animal a) {
a.move();
}
public static void main(String[] args) {
Animal[] animals = {new Animal(), new Horse(), new Animal()};
for (Animal a:animals) {
move(a);
Horse h = (Horse)a;
h.jump();
}
}
}
這段代碼可以(居然可以!!)通過編譯,可是啟動並執行時候就會出錯:
Animal: generic move
Exception in thread "main" java.lang.ClassCastException: com.my.Animal cannot be cast to com.my.Horse
at com.my.TestAnimals.main(TestAnimals.java:12)
oop!你也許會抱怨為什麼編譯器不能發現這個錯誤,因為很明顯, 怎能把一個Horse對象轉換成一個Animal對象呢? 難道是把對象放在數組中編譯器發現不了? 事實上編譯器允許你這樣做:
Animal a = new Animal()
Horse h = (Horse)a;
這是因為編譯器只能確定兩個對象是否在一棵類型樹中,而無法檢測它們具體的關係。假如你嘗試這樣做:
Animal a= new Animal()
Stirngs s = (String)a;
編譯器會提示你無法把一個Animal類型轉換為String類型。 但是當待轉換類型和轉換後的類型是在同一棵類型樹的時候,情況就不同了,因為它有可能是正確的,也有可能是錯誤的,一切只有等到運行時才能發現。再來看以下代碼:
(假設Horse有一個FlyableHorse的子類)
Animal a = new FlyableHorse()
Horse h = (Horse)a;
這兩行代碼可以通過編譯,而且運行時也不會出錯。因為a 指向的是Horse的子類FlyableHorse, 當然可以把一個FlyableHorse對象裁剪為一個Horse對象。 記住, 一切都在啟動並執行時候才知道!!!
所以我們可以總結如下:
在進行向上裁剪時,一切都是自然而然的,安全的,你不需要做任何東西
在進行向下裁剪時,你必須顯示的進行類型轉換,而且你知道你現在做的事情是正確的。 編譯器不會幫你糾正錯誤,一切只有啟動並執行時候才知道。