Java基礎之多型
之前我們的文章講了Java的封裝和繼承,封裝講的時候,並沒有體現出來封裝的強大之處,反而還要慎用封裝。因為這時的封裝還沒有和多型聯絡到一起,還無法看出向上轉型的厲害之處。 多型,是指同一個行為具有多種的表現形式。同一個方法根據呼叫物件的不同而產生多種結果。對於Java而言,多型就是程式中定義的引用變數,和呼叫方法的程式碼在編譯的時候就決定好了,但引用變數所指向的物件,卻是在執行時才確定的。舉一個很簡單的例子,人要工作。這裡工作是一個方法,但一個作家工作就是寫文章,一個程式設計師工作卻是寫程式碼。工作的執行者不同,工作的內容也不同。這就是一種多型。
多型的實現
對於我們而言,實現多型要如下的準備。
- 繼承
- 方法覆寫
- 向上轉型
對於Java ,則是通過動態繫結來實現。我們先理解一下什麼是動態繫結,才方便後面去利用多型的特性。
動態繫結
繫結是指將一個方法呼叫同一個方法主體關聯起來。 動態繫結是值在執行時根據物件的型別進行繫結,而與之對應就是靜態繫結,在編譯期就進行繫結,在Java裡面只有final static private 和構造方法是靜態繫結的。 我們已經知道了一般方法的呼叫是在執行時根據物件的型別進行呼叫的,換句話說,不管你在程式碼裡是怎麼寫的,在執行時才會真正決定呼叫方法的物件是哪個。
實現形式
之前繼承的文章裡面,我們已經知道是可以通過向上轉型將一個子類轉型成父類,那麼我們就可以寫出下面的程式碼
Animal animal = new Dog();
如果Dog覆寫了Animal中的方法,那麼呼叫這個被覆寫的方法時根據上面的動態繫結的規則,我們就知道實際呼叫的物件是Dog,那麼執行的也會是Dog類的那個方法。具體的例子如下:
public class Animal { public void run() { System.out.println("動物在奔跑"); } } public class Cat extends Animal { @Override public void run() { System.out.println("貓在奔跑"); } } public class Dog extends Animal{ @Override public void run() { System.out.println("狗在奔跑"); } public static void main(String[] args) { Animal[] animal = {new Dog(),new Cat()}; animal[0].run(); animal[1].run(); } } //執行的結果 狗在奔跑 貓在奔跑
在這個例子裡面,我們可以看到多型的應用,在父類中定義一個通用的run方法,在子類中去實現不同的run方法,然後將子類的物件賦給父類的引用,就可以根據子類的不同 去呼叫不同的run方法了。這裡面,多型的實現滿足了上面的三點,首先是繼承關係,其次,在子類中覆寫父類的方法,最後,對子類的物件進行向上轉型,產生多型。
多型的好處
我們在上個例子基礎上在新增一段程式碼:
public class Log {
public void print(Animal animal) {
System.out.println(new Date().toString());
animal.run();
}
}
//我們將Dog類,修改成下面這個樣子
public class Dog extends Animal{
@Override
public void run() {
System.out.println("狗在奔跑");
}
public static void main(String[] args) {
Animal[] animal = {new Dog(),new Cat()};
for (Animal item : animal) {
new Log().print(item);
}
}
}
執行的結果如下:
Wed Jul 13 17:07:40 CST 2016
狗在奔跑
Wed Jul 13 17:07:40 CST 2016
貓在奔跑
在上面的例子中,如果我們不採用多型,那麼我們在Log中就要根據不同的物件,生成不同print方法,這樣修改起來異常的麻煩。而採用多型的話,我們只需要在方法中編寫父類的物件呼叫方法的程式碼,但在執行時傳入子類的物件,就會動態繫結到子類上,呼叫子類實現的方法。這樣做,在新增新的動物類的時候,我們也不用修改我們的print方法,不用改變已經編寫好的程式碼,那麼也不會影響已經實現的方法。多型成功的區分開了"改變的事物和未改變的事物"。這給我們程式設計帶來極大的便利。