天天看點

Java入門記(二):向上轉型與向下轉型幾個同義詞 向上轉型與向下轉型 轉型的誤區

在對Java學習的過程中,對于轉型這種操作比較迷茫,特總結出了此文。例子參考了《Java程式設計思想》。

    目錄

幾個同義詞

向上轉型與向下轉型

  例一:向上轉型,調用指定的父類方法

  例二:向上轉型,動态綁定

  例三:向上轉型,靜态綁定

  例四:向下轉型

轉型的誤區

  1.運作資訊(RTTI)

  2.數組類型

  3.Java容器

幾個同義詞

  首先是幾組同義詞。它們出現在不同的書籍上,這是造成了解混淆的原因之一。

  父類/超類/基類

  子類/導出類/繼承類/派生類

  靜态綁定/前期綁定

  動态綁定/後期綁定/運作時綁定

向上轉型與向下轉型

例一:向上轉型,調用指定的父類方法

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);
    }
}      
Java入門記(二):向上轉型與向下轉型幾個同義詞 向上轉型與向下轉型 轉型的誤區

輸出為

Shape draw.

  這表明,draw(Shape s)方法本來被設計為接受Shape引用,但這裡傳遞的是Circle引用。實際上draw(Shape s)方法可以對所有Shape類的導出類使用,這被稱為向上轉型。表現的行為,和方法所屬的類别一緻。換句話說,由于明确指出是父類Shape的方法,那麼其行為必然是這個方法對應的行為,沒有任何歧義可言。

  “向上轉型”的命名來自于類繼承圖的畫法:根置于頂端,然後逐漸向下,以本例中兩個類為例,如下圖所示:

Java入門記(二):向上轉型與向下轉型幾個同義詞 向上轉型與向下轉型 轉型的誤區

例二:向上轉型,動态綁定

Java入門記(二):向上轉型與向下轉型幾個同義詞 向上轉型與向下轉型 轉型的誤區
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);
    }
}      
Java入門記(二):向上轉型與向下轉型幾個同義詞 向上轉型與向下轉型 轉型的誤區

輸出為

  Circle draw.

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

例三:向上轉型,靜态綁定

Java入門記(二):向上轉型與向下轉型幾個同義詞 向上轉型與向下轉型 轉型的誤區
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);
    }
}      
Java入門記(二):向上轉型與向下轉型幾個同義詞 向上轉型與向下轉型 轉型的誤區

輸出為

  Shape draw.

  例三與例二有什麼差別?細看之下才會發現,例三裡調用的方法被static修飾了,得到了完全不同的結果。

  這兩例行為差别的原因是:Java中除了static方法和final方法(包括private方法),其他方法都是動态綁定的。對于一個傳入的基類引用,後期綁定能夠正确的識别其所屬的導出類。加了static,自然得不到這個效果了。

  了解了這一點之後,就可以明白為什麼要把例一寫出來了。例一中的代碼明确指出調用父類方法,而例三調用哪個方法是靜态綁定的,不是直接指明的,稍微繞了一下。

例四:向下轉型

  出自《Java程式設計思想》8.5.2節,稍作了修改,展示如何通過類型轉換獲得子類獨有方法的通路方式。

  這相當于告訴了編譯器額外的資訊,編譯器将據此作出檢查。

Java入門記(二):向上轉型與向下轉型幾個同義詞 向上轉型與向下轉型 轉型的誤區
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
    }
}          
Java入門記(二):向上轉型與向下轉型幾個同義詞 向上轉型與向下轉型 轉型的誤區

輸出

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節的例子,這裡不做詳述:

Java入門記(二):向上轉型與向下轉型幾個同義詞 向上轉型與向下轉型 轉型的誤區
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
    }
}      
Java入門記(二):向上轉型與向下轉型幾個同義詞 向上轉型與向下轉型 轉型的誤區

轉型的誤區

  轉型很友善,利用轉型可以寫出靈活的代碼。不過,如果用得随心所欲而忘乎是以的話,難免要跌跟頭。下面是幾種看似可以轉型,實際會導緻錯誤的情形。

1.運作資訊(RTTI)

/* 本例代碼節選自《Java程式設計思想》14.2.2節 */

Class<Number> genericNumberClass = int.class      

  這段代碼是無效的,編譯不能通過,即使把int換為Integer也同樣不通過。雖然int的包裝類Integer是Number的子類,但Integer Class對象并不是Number Class對象的子類。

2.數組類型

Java入門記(二):向上轉型與向下轉型幾個同義詞 向上轉型與向下轉型 轉型的誤區
/* 代碼節改寫《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入門記(二):向上轉型與向下轉型幾個同義詞 向上轉型與向下轉型 轉型的誤區

  注釋部分在去掉注釋後運作會提示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入門記(二):向上轉型與向下轉型幾個同義詞 向上轉型與向下轉型 轉型的誤區
/* 代碼節選自《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>();
    }
}      
Java入門記(二):向上轉型與向下轉型幾個同義詞 向上轉型與向下轉型 轉型的誤區

  明明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,如需轉載請自行聯系原作者