我們之前使用類別創造新的類型(type),並使用繼承來便利我們創建類別的過程。我將在這一講中深入類型,並介紹多態(polymorphism)的概念。
類型檢查
Java的任意變數和引用經過型別宣告(type declaration),才能使用。我們之前見過物件資料、類別資料、方法參數、方法傳回值以及方法內部的自動變量,它們都需要聲明其類型。 Java是一種強型別(strongly typing)語言,它會對型別進行檢查。如果我們錯誤的使用類型,將造成錯誤。
類型不符,賣萌無效
例如在下面的Test類別中,我們將一個Cup類別物件賦予給aPerson類別參考:
public class Test{ public static void main(String[] args) { Human aPerson; aPerson = new Cup(); }}class Human{ /** * constructor */ public Human(int h) { this.height = h; } /** * accessor */ public int getHeight() { return this.height; } /** * mutator */ public void growHeight(int h) { this.height = this.height + h; } private int height;}class Cup { public void addWater(int w) { this.water = this.water + w; } public void drinkWater(int w) { this .water = this.water - w; } private int water = 0;}
javac將返回:
found : Cuprequired: Human aPerson = new Cup(); ^1 error
基本型別轉換
Java可以對基本型別的變數進行型別轉換。不同的基本類型有不同的長度和儲存範圍。如果我們從一個高精度類型轉換到低精度類型,例如從float轉換到int,那麼我們有可能會損失資訊。這樣的轉換叫做收縮變換(narrowing conversion)。這種情況下,我們需要顯示的聲明類型轉換,例如:
public class Test{ public static void main(String[] args) { int a; a = (int) 1.23; // narrowing conversion System.out.println(a); }}
如果我們從低精度類型轉換成高精度類型,則不存在資訊損失的顧慮。這樣的變換叫做寬鬆變換(widening conversion)。我們不需要顯示的要求類型轉換,Java可以自動進行:
public class Test{ public static void main(String[] args) { int a = 3; double b; b = a; // widening conversion System.out.println(a); }}
基本型別轉換
upcast與多型
在Java中,引用也可以進行型別轉換,但有限制。
我們可以將一個衍生類別引用轉換為其基底類別引用,這叫做向上轉換(upcast)或寬鬆轉換。下面的BrokenCup類別繼承自Cup類,並覆寫了Cup類別中原有的addWater()和drinkWater()方法:
public class Test{ public static void main(String[] args) { Cup aCup; BrokenCup aBrokenCup = new BrokenCup(); aCup = aBrokenCup; // upcast aCup.addWater(10); // method binding }}class Cup { publicgin aCup.addWater(10); // method binding }}class Cup { publicpublicding }}class Cup { publicding} void addWater(int w) { this.water = this.water + w; } public void drinkWater(int w) { this.water = this.water - w; } private int water = 0;}class BrokenCup extends Cup{ public void addWater(int w) { System.out.println("shit, broken cup" ); } public void drinkWater(int w) { System.out.println("om...num..., no water inside"); }}
程式運行結果:
shit, broken cup
在上面可以看到,不需要任何顯示說明,我們將衍生類別引用aBrokenCup賦予給它的基底類別引用aCup。類型轉換將由Java自動進行。
我們接著呼叫了aCup(我們宣告它為Cup型別)的addWater()方法。儘管aCup是Cup類型的引用,它實際上調用的是BrokenCup的addWater()方法!也就是說,即使我們經過upcast,將引用的類型寬鬆為其基類,Java仍然能正確的識別物件本身的類型,並呼叫正確的方法。 Java可以根據目前狀況,辨識物件的真實類型,這叫做多態性(polymorphism)。多態是物件導向的一個重要面向。
多態是Java的支援的一種機制,同時也是物件導向的重要概念。這提出了一個分類學的問題,既子類別物件實際上「是」父類別物件。例如一隻鳥,也是一個動物;一輛汽車,也必然是一個交通工具。 Java告訴我們,一個衍生類別物件可以當做一個基類物件使用,而Java會正確的處理這種情況。
比如下面的繼承關係:
我們可以說用杯子(Cup)喝水(drinkWater)。實際上,喝水這個動作具體意義會在衍生類別中發生很大變換。例如用吸管喝水,和從一個破杯子喝水,這兩個動作差別會很大,雖然我們抽像中都講「喝水」。我們當然可以針對每個衍生類別分別編程,呼叫不同的drinkWater方法。然而,身為程式設計師,我們可以對杯子編程,呼叫Cup的drinkWater()方法,而無論這個杯子是什麼樣的衍生類別杯子。 Java會呼叫對應的正確方法,正如我們在上面程式中所看到的。
來看一個更有意義的例子,我們為Human類別增加一個drink()方法,這個方法接收一個杯子物件和一個整數作為參數。整數表示喝水的水量:
public class Test{ public static void main(String[] args) { Human guest = new Human(); BrokenCup hisCup = new BrokenCup(); guest.drink(hisCup, 10); }}class Human { void drink(Cup aCup , int w) { aCup.drinkWater(w); }}
程式運行結果:
shit, no water inside
我們在Human類別的drink()的定義中,要求第一個參量為Cup型別的引用。但在實際運用時(Test類別),將Cup的BrokenCup衍生類別物件。這其實是將hisCup向轉型稱為Cup類,傳遞給drink()方法。在方法中,我們呼叫了drinkWater()方法。 Java發現這個物件其實是BrokenCup對象,所以實際上呼叫了BrokenCup的對應方法。
downcast
我們可以將一個基類引用向下轉型(downcast)成為衍生類別的引用,但要求該基類引用所指向的對象,已經是所要downcast的衍生類對象。例如可以將上面的hisCup向上轉型為Cup類別引用後,再向下轉型成為BrokenCup類別引用。
Object類型
Java中,所有的類別其實都有一個共同的繼承祖先,也就是Object類別。 Object類別提供了一些方法,例如toString()。我們可以在自己的類別定義中覆寫這些方法。
Object: 祖先
我們可以寫一個操作Object物件的程序,就可以透過upcast,將任意物件傳遞給該程式。
我將在以後深入Object類別。
(多態的實現是依靠RTTI的支持。我將在以後深入。)
總結
基本型別轉換
polymorphism
downcast
Object