2017年11月4日 星期六

Day2-5 Casting - Reference variable 轉型

5.2 Given a scenario, develop code that demonstrates the use of polymorphism. Further, determine when casting will be necessary and recognize compiler vs. runtime errors related to object reference casting. 

在下面例子中,若已經知道某個“可能是”Truck的Object混在Vehicle陣列v內,要如何做才能讓這個Truck object執行Truck的專屬method(carryCargo())?
class Vehicle {
 void run() {
  System.out.println("Vehicle run");
 }
}
class Truck extends Vehicle {
 void run() {
  System.out.println("Truck run");
 }
 void carryCargo() {
  System.out.println("carryCargo");
 }
}
class CastTest {
 public static void main(String[] args) {
  Vehicle[] v = {
   new Vehicle(),
   new Truck(),
   new Vehicle()
  };
  for (Vehicle vehicle: v) {
   vehicle.run();
   if (vehicle instanceof Truck) {
    vehicle.carryCargo(); //卡車專屬的method
   }
  }
 }
}

如上,會有Compiler error - cannot find symbol。原因是編譯器認為,Vehicle Class並沒有carryCargo()可以使用。這時候方法其實就是Casting來轉型。


if (vehicle instanceof Truck) {
 Truck t = (Truck) vehicle;//Casting這個reference variable,變成Truck
 t.carryCargo(); 
}

另外上面的cast程式碼也可以寫成 ((Truck)vehicle).carryCargo();
此時,編譯器必須看到所有的括號,否則它會認為這段程式碼是不完整的。
從superClass的型態轉成比較narrow的superClass型態稱為downcast。在使用downcast的時候可以使用instanceof來測試,如果成功來才做轉型,避免失敗。

有時Compiler會選擇相信程式碼,比如下面的範例:
Vehicle v1 = new Vehicle();
Truck t = (Truck) v1;

Compiler並不會報錯,但在runtime的時候會有Exception跳(java.lang.ClassCastException),說明無法轉型。原因是Compiler會檢查Truck和Vehicle是否在同一個inheritance tree上,而且在轉型之前的程式碼其實也是有可能成功運行的。Compiler會讓可能運行成功的程式碼通過。
當然,如果非常明顯cast會有問題的程式碼,Compiler還是會擋住(比如他們不是在同一個inheritance tree上)

以下示範upcast(向上轉型):

Truck t = new Truck(); 
Vehicle v1 = t; // 隱晦的轉型
Vehicle v2 = (Vehicle) t; // 明確的轉型

上面兩種寫法都compile以及runtime都會成功,且結果也一樣。其理由為,Truck IS-A Vehicle,這也表示Vehicle能做的事情,Truck一定可以做。Compiler和JVM都知道,所以就算沒有明確的cast也是可以的。

試想情境:
假設Baby extends Human,且Human extends Animal (Baby->Human->Animal),則其實Baby也是extends Animal的,因為Baby IS-A Human,繼承關係也代表Baby IS-A Animal。
若有個Mammals Interface被Human implements(Human --> Mammals)且實作了,一旦Baby extends Human,這表示Baby也有Mammals的method可以使用了。就算在Baby再一次宣告 implements Mammals,也是可以的(為了讓其他人可以從API知道Baby IS-A Mammals,而不需要去看Human API),且Baby不需要再實作一次Mammals內的method也可以,因為Human已經幫忙實作了。當然Baby可以重新定義從Human那繼承來的Mammals method。

結論:
一個concrete class如果implements interface,但卻沒有implements interface內的abstract class,並不代表有問題,必需往上看他所有的superclass是否已經曾經實作過了。假如superclass的其中一個節點已經實作過,那麼下面的子子孫孫不需要再實作一次了。


沒有留言:

張貼留言