在對Java學習的過程中,對于轉型這種操作比較迷茫,特總結出了此文。例子參考了《Java程式設計思想》。
目錄
幾個同義詞
向上轉型與向下轉型
例一:向上轉型,調用指定的父類方法
例二:向上轉型,動态綁定
例三:向上轉型,靜态綁定
例四:向下轉型
轉型的誤區
1.運作資訊(RTTI)
2.數組類型
3.Java容器
幾個同義詞
首先是幾組同義詞。它們出現在不同的書籍上,這是造成了解混淆的原因之一。
父類/超類/基類
子類/導出類/繼承類/派生類
靜态綁定/前期綁定
動态綁定/後期綁定/運作時綁定
向上轉型與向下轉型
例一:向上轉型,調用指定的父類方法

class Shape {
static void draw(Shape s) {
System.out.println("Shape draw.");
}
}
class Circle extends Shape {
static void draw(Circle c) {
System.out.println("Circle draw.");
}
}
public class CastTest {
public static void main(String args[]) {
Circle c = new Circle();
Shape.draw(c);
}
}

輸出為
Shape draw.
這表明,draw(Shape s)方法本來被設計為接受Shape引用,但這裡傳遞的是Circle引用。實際上draw(Shape s)方法可以對所有Shape類的導出類使用,這被稱為向上轉型。表現的行為,和方法所屬的類别一緻。換句話說,由于明确指出是父類Shape的方法,那麼其行為必然是這個方法對應的行為,沒有任何歧義可言。
“向上轉型”的命名來自于類繼承圖的畫法:根置于頂端,然後逐漸向下,以本例中兩個類為例,如下圖所示:
例二:向上轉型,動态綁定

class Shape {
public void draw() {
System.out.println("Shape draw.");
}
}
class Circle extends Shape {
public void draw() {
System.out.println("Circle draw.");
}
}
public class CastTest {
public static void drawInTest(Shape s) {
s.draw();
}
public static void main(String args[]) {
Circle c = new Circle();
drawInTest(c);
}
}

輸出為
Circle draw.
這樣做的原因是,一個drawInTest(Shape s)就可以處理Shape所有子類,而不必為每個子類提供自己的方法。但這個方法能能調用父類和子類所共有的方法,即使二者行為不一緻,也隻會表現出對應的子類方法的行為。這是多态所允許的,但容易産生迷惑。
例三:向上轉型,靜态綁定

class Shape {
public static void draw() {
System.out.println("Shape draw.");
}
}
class Circle extends Shape {
public static void draw() {
System.out.println("Circle draw.");
}
}
public class CastTest {
public static void drawInTest(Shape s) {
s.draw();
}
public static void main(String args[]) {
Circle c = new Circle();
drawInTest(c);
}
}

輸出為
Shape draw.
例三與例二有什麼差別?細看之下才會發現,例三裡調用的方法被static修飾了,得到了完全不同的結果。
這兩例行為差别的原因是:Java中除了static方法和final方法(包括private方法),其他方法都是動态綁定的。對于一個傳入的基類引用,後期綁定能夠正确的識别其所屬的導出類。加了static,自然得不到這個效果了。
了解了這一點之後,就可以明白為什麼要把例一寫出來了。例一中的代碼明确指出調用父類方法,而例三調用哪個方法是靜态綁定的,不是直接指明的,稍微繞了一下。
例四:向下轉型
出自《Java程式設計思想》8.5.2節,稍作了修改,展示如何通過類型轉換獲得子類獨有方法的通路方式。
這相當于告訴了編譯器額外的資訊,編譯器将據此作出檢查。

class Useful {
public void f() {System.out.println("f() in Useful");}
public void g() {System.out.println("g() in Useful");}
}
class MoreUseful extends Useful {
public void f() {System.out.println("f() in MoreUseful");}
public void g() {System.out.println("g() in MoreUseful");}
public void u() {System.out.println("u() in MoreUseful");}
}
public class RTTI {
public static void main(String[] args) {
Useful[] x = {
new Useful(),
new MoreUseful()
};
x[0].f();
x[1].g();
// Compile-time: method not found in Useful:
//! x[1].u();
((MoreUseful)x[1]).u(); // Downcast/RTTI
((MoreUseful)x[0]).u(); // Exception thrown
}
}

輸出
Exception in thread "main" java.lang.ClassCastException: Useful cannot be cast to MoreUseful
at RTTI.main(RTTI.java:44)
f() in Useful
g() in MoreUseful
u() in MoreUseful
雖然父類Useful類型的x[1]接收了一個子類MoreUseful對象的引用,但仍然不能直接調用其子類中的u()方法。如果需要調用,需要做向下轉型。這種用法很常見,比如一個通用的方法,處理的入參是一個父類,處理時根據入參的類型資訊轉化成對應的子類使用不同的邏輯處理。
此外,父類對象不能向下轉換成子類對象。
向下轉型的好處,在學習接口時會明顯地體會出來(如果把實作接口看作多重繼承)。可以參考9.4節的例子,這裡不做詳述:

interface CanFight {
void fight();
}
interface CanSwim {
void swim();
}
interface CanFly {
void fly();
}
class ActionCharacter {
public void fight() {}
}
class Hero extends ActionCharacter implements CanFight, CanSwim, CanFly {
public void swim() {}
public void fly() {}
}
public class Adventure {
static void t(CanFight x) { x.fight(); }
static void u(CanSwim x) { x.swim(); }
static void v(CanFly x) { x.fly(); }
static void w(ActionCharacter x) { x.fight(); }
public static void main(String[] args) {
Hero i = new Hero();
t(i); // Treat it as a CanFight
u(i); // Treat it as a CanSwim
v(i); // Treat it as a CanFly
w(i); // Treat it as an ActionCharacter
}
}

轉型的誤區
轉型很友善,利用轉型可以寫出靈活的代碼。不過,如果用得随心所欲而忘乎是以的話,難免要跌跟頭。下面是幾種看似可以轉型,實際會導緻錯誤的情形。
1.運作資訊(RTTI)
/* 本例代碼節選自《Java程式設計思想》14.2.2節 */
Class<Number> genericNumberClass = int.class
這段代碼是無效的,編譯不能通過,即使把int換為Integer也同樣不通過。雖然int的包裝類Integer是Number的子類,但Integer Class對象并不是Number Class對象的子類。
2.數組類型

/* 代碼節改寫《Java程式設計思想》15.8.2節,本例與泛型與否無關。 */
class Generic<T> {}
public class ArrayOfGeneric {
static final int SIZE = 100;
static Generic<Integer>[] gia;
@SuppressWarnings("unchecked")
public static void main(String[] args) {
//! gia = (Generic<Integer>[]) new Object[SIZE];
gia = (Generic<Integer>[]) new Generic[SIZE];
}
}

注釋部分在去掉注釋後運作會提示java.lang.ClassCastException。這裡令人迷惑的地方在于,子類數組類型不是父類數組類型的子類。在異常提示的後面可以看到
[Ljava.lang.Object; cannot be cast to [LGeneric;
除了通過控制台輸出的異常資訊,可以使用下面的代碼來看看gia究竟是什麼類型:
Object[] obj = new Object[SIZE];
gia = (Generic<Integer>[]) new Generic[SIZE];
System.out.println(obj.getClass().getName());
System.out.println(gia.getClass().getName());
System.out.println(obj.getClass().getClass().getName());
System.out.println(gia.getClass().getSuperclass().getName());
控制台輸出為:
[Ljava.lang.Object;
[LGeneric;
java.lang.Object
java.lang.Object
可見,由Generic<Integer>[] gia和Object[] obj定義出的gia和obj根本沒有任何繼承關系,自然不能類型轉換,不管這個數組裡是否放的是子類的對象。(子類對象是可以通過向上轉型獲得的,如果被轉換的确實是一個子類對象,見例四)
3.Java容器

/* 代碼節選自《Java程式設計思想》15.10節*/
class Fruit {}
class Apple extends Fruit {}
class Orange extends Fruit {}
public class Test {
public static void main(String[] args) {
// 無法編譯
List<Fruit> fruitList = new ArrayList<Apple>();
}
}

明明Fruit的List是可以存放Apple對象的,為什麼指派失敗?其實這根本不是向上轉型。雖然可以通過getClass().getName()得知List<Fruit>和List<Apple>同屬java.util.ArrayList類型,但是,假設這裡可以編譯通過,相當于允許向ArrayList<Apple>存放一個Orange對象,顯然是不合理的。雖然由于泛型的擦除,ArrayList<Fruit>和ArrayList<Apple>在運作期是同一種類型,但是具體能持有的元素類型會在編譯期進行檢查。
本文轉自五嶽部落格園部落格,原文連結:http://www.cnblogs.com/wuyuegb2312/p/java-cast.html,如需轉載請自行聯系原作者