1. 程式人生 > >Java子類與父類之間的物件轉換

Java子類與父類之間的物件轉換

在使用Java的多型機制時,常常使用的一個特性便是子類和父類之間的物件轉換。從子類向父類的轉換稱為向上轉換(upcasting),通過向上轉換,我們能夠在編寫程式時採用通用程式設計的思想,在需要使用子類物件的時候,通過把變數定義為父型別,我們可以通過一個變數,使用該父型別的所有子型別例項;從父型別向子型別的轉換稱為向下轉換(downcasting),通過向下轉換,我們能在必要的時候,將父型別變數轉換成子型別變數,使用一些通過子型別才能夠使用的方法。以下是我對於物件轉換的一些個人理解,如有不對,歡迎指正,虛心向大神們請教。

首先是從子類向父類的向上轉換。向上轉換比較直觀,總是能夠將一個子類的例項轉換為一個父類的物件,從繼承鏈的角度,這個特性很容易理解:繼承是一種“是一種”的關係,從父類派生出的子類,我們都能理解為,子類總是父類的一個例項。比如說,Fruit類派生出了Orange類,Apple類,Orange和Apple都是Fruit;Animal類派生出了Tiger類和Lion類,Tiger和Lion都是Animal。因此,從子類向父類的轉換不需要什麼限制,只需直接將子類例項賦值給父類變數即可,這也是Java中的多型的實現機制。

//Test.java
public class Test {
    public static void main(String args[]) {
        Animal tiger = new Tiger();
        Animal lion = new Lion();
    }
}

class Animal {
    String name;
    Animal() {
        name = "animal";
    }
    Animal(String name) {
        this.name = name;
    }
}
class
Tiger extends Animal {
Tiger() { super("tiger"); } } class Lion extends Animal { Lion() { super("lion"); } }

然而從父類向子類的向下轉換就稍微複雜一些了。在講述向下轉換之前,也許有些剛學java的朋友會有點不解為什麼要使用向下轉換,使用多型和動態繫結機制通過父型別變數使用子變數不就可以了麼(比如我就曾對此感到疑惑)。這就要考慮到,在繼承關係中,有一些方法是不適合由父類定義並由子類繼承並重寫的,有些方法是子類特有的,不應該通過繼承得到,且子類可能也會有自己特有的成員變數,那麼在使用多型機制的時候,若我們要通過父型別變數使用到這些子類特有的方法和屬性的話,就需要將服型別變數轉換成對應的子型別變數。一個典型例子便是標準庫中的資料型別包裝類:Integer類,Double類,Long類等,它們都繼承自Number類,且它們都有一個方法叫做compareTo用於比較兩個同樣的型別。然而這個方法是這些子類通過實現Comparable介面來實現的,在Number類中並沒有該方法的實現,因此若要通過Number型別變數來使用compareTo方法,就要先將Number類轉換成子類的物件。

從父類向子類的轉換就有限制了。首先,父類變數向子類轉換必須通過顯式強制型別轉換,採取和向上轉換相同的直接賦值方式是不行的,;並且,當把一個父型別變數例項轉換為子型別變數時,必須確保該父類變數是子類的一個例項,從繼承鏈的角度來理解這些原因:子類一定是父類的一個例項,然而父類卻不一定是子類的例項;比如說,Fruit未必是Orange,它可能是Apple;Animal也不一定是Tiger,它可能是Lion。用程式碼來解釋一下:

Animal tiger = new Tiger();
Animal lion = new Lion();

在前面向上轉換的程式碼示例當中,main方法中的這兩行程式碼,意思就是父型別變數tiger是子類Tiger的一個例項,lion是Lion的一個例項。
也就是說,如果要把tiger轉換為Tiger型別,必須保證tiger本身是Tiger的一個例項,在上例中,如果要把tiger轉換成Lion型別,或是把Lion型別轉換為Tiger型別,都是行不通的,在執行時,這會丟擲一個執行異常ClassCastException,表示類轉換異常。因此,在進行父類向子類的轉換時,一個好的習慣是通過instanceof運算子來判斷父類變數是否是該子類的一個例項:

Tiger t = null;
if(tiger instanceof Tiger)
    t = (Tiger)tiger;

如果要通過父類呼叫子類變數的方法,那麼要注意要將父型別變數和強制轉換用括號括起來:

Number i = new Integer(3);
System.out.println(
    ((Integer)i).compareTo(new Integer(4))
                  );

因為成員訪問運算子.的優先順序大於型別轉換,所以要用括號括起來保證型別轉換先於成員訪問進行運算。
前面說到用instanceof判斷父類是否是子類的一個例項是一個好習慣,如果不養成這個習慣的話很容易出問題,請看下面這段程式碼:

Animal animal = new Tiger();
Lion lion = (Lion)tiger;

前面說到,這段程式碼會在執行時丟擲ClassCastException異常,然而,這段程式碼卻是能夠編譯成功的。原因是因為,Java編譯器並沒有聰明到能夠在編譯階段就知道父型別變數是哪一個子類的例項,所以,將animal轉換為Lion型別的程式碼:(Lion)animal是能夠編譯通過的,即使事實上我們能看到animal是Tiger的一個例項,因為Animal型別確實能轉換成Lion型別,所以這條語句是合法的。所以,如果沒有使用instanceof防止不同子型別之間的物件轉換,而又不能指望編譯器檢查出這種轉換邏輯錯誤的話,就很容易犯錯了。