多态性
面向對象(OOP)三大特性:封裝、繼承、多态。
多态(polymorphism)指同一行為具有多種不同表現形式,在面向對象程式設計中表現為同一消息可以根據發送對象的類型不同,做出多種不同的行為。
多态的優點
多态性能夠從一定程度上消除類型之間的耦合關系,通過統一接口方式,不同類的對象可以直接替換,程式更加靈活,可擴充。
多态存在的三個必要條件
- 繼承
- 重寫
- 父類引用指向子類對象
多态的實作方式
重寫(Override)與重載(Overload)
方法重載(Method Overloading)
方法重載(Method Overloading)允許類具有多個相同名稱的方法,但是方法參數清單不同。
重載形式:
case 1: 參數數量變化(有效)
add(int, int)
add(int, int, int)
case 2: 參數資料類型變化(有效)
add(int, int)
add(int, float)
case 3: 參數資料類型順序變化(有效)
add(int, float)
add(float, int)
bad case 1: 僅改變傳回類型(無效)
int add(int, int)
float add(int, int)
Java 方法簽名由方法名和其後的參數清單共同決定,僅改變傳回類型編譯器無法重載。
方法重載(Method Overloading)允許改變傳回類型和存取權限。
方法重載(Method Overloading)式多态性,即方法調用取決于調用時傳遞的參數(數量、類型、順序),屬于編譯時靜态多态性。
方法重寫(Method Overriding)
方法重寫(Method Overriding)允許子類對父類可以通路的方法,實作自定義行為。重寫的優點在于,無需修改父類代碼即可改變子類繼承的方法。
重寫形式:
重寫依賴繼承,通過父類引用,指向子類對象實作動态多态性。
public class Animal{
public void sound(){
System.out.println("Animal is making a sound");
}
}
public class Cat extends Animal{
@Override
public void sound(){
System.out.println("Meow");
}
public static void main(String args[]){
Animal obj = new Cat();
obj.sound();
}
}
輸出:
Meow
重寫(覆寫)規則
- 參數清單必須一樣,傳回類型需要相容。
- 不能降低方法的存取權限。
- static, private, final 标記的方法以及類的構造方法不能被重寫覆寫。
靜态綁定與動态綁定
多态的類型可以分為運作時和編譯時,方法重寫(Method Overriding)代表運作時動态多态性,方法重載(Method Overloading)代表編譯時靜态多态性。
方法調用與方法體的關聯稱為綁定,有兩種類型的綁定:在編譯時發生的靜态綁定(Static Binding or Early Binding)和在運作時發生的動态綁定(Dynamic Binding or Late Binding)。
static, private, final 标記的方法以及類的構造方法是編譯時靜态綁定的。因為此類方法無法覆寫,并且類的類型在編譯時即可确定。其他非标記的方法可以稱為“虛函數”,Java 中其實并沒有“虛函數”的概念,所有普通函數(方法)預設都相當于 C++ 的”虛函數”允許覆寫(Override),是以虛函數(virtual method)能夠根據運作時的具體對象進行動态綁定實作動态多态性,例如方法重寫(Method Overriding)。
靜态綁定示例:
class Human{
public static void walk()
{
System.out.println("Human walks");
}
}
class Boy extends Human{
public static void walk(){
System.out.println("Boy walks");
}
public static void main( String args[]) {
/* Reference is of Human type and object is
* Boy type
*/
Human obj = new Boy();
/* Reference is of Human type and object is
* of Human type.
*/
Human obj2 = new Human();
obj.walk();
obj2.walk();
}
}
Human walks
Human walks
聲明為 static 的方法不能被重寫,但是能夠被再次聲明。
Static Binding vs Dynamic Binding
- 靜态綁定發生在編譯時,而動态綁定發生在運作時。
- 靜态綁定使用的是類資訊:類的類型決定調用方法,而動态綁定使用的是對象資訊:對象的類型決定調用方法。
- 方法重載使用靜态綁定,而方法重寫使用動态綁定。
綜合練習
多态示例:
class A {
public String show(D obj) { // 方法一
return ("A and D");
}
public String show(A obj) { // 方法二
return ("A and A");
}
}
class B extends A {
public String show(B obj) { // 方法三
return ("B and B");
}
public String show(A obj) { // 方法四
return ("B and A");
}
}
class C extends B {
}
class D extends B {
}
public class Main {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + a1.show(b));
System.out.println("2--" + a1.show(c));
System.out.println("3--" + a1.show(d));
System.out.println("4--" + a2.show(b));
System.out.println("5--" + a2.show(c));
System.out.println("6--" + a2.show(d));
System.out.println("7--" + b.show(b));
System.out.println("8--" + b.show(c));
System.out.println("9--" + b.show(d));
}
}
運作結果:
1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D
詳細分析:
A、B、C、D 各類繼承關系如圖所示:
-
正常建立對象 a1,涉及函數重載 show(),a1 具有調用方法一 show(D obj) 和方法二 show(A obj) 的能力。A a1 = new A();
由編譯器進行靜态綁定(前期綁定)方法二 show(A obj)。a1.show(b)
-
a1.show(c)
-
由編譯器進行靜态綁定(前期綁定)方法一 show(D obj)。a1.show(d)
-
多态建立父類引用,指向子類對象,a2 向上轉型具有調用 A 類方法一 show(D obj) 和方法二 show(A obj) 的能力,其中子類 B 重寫父類 A 的方法二 show(A obj) 為方法四 show(A obj)。記住向上轉型存在缺點,即不能調用子類中有,父類沒有的方法,如方法三 show(B obj)。A a2 = new B();
運作時動态綁定(後期綁定)方法四 show(A obj)。a2.show(b)
-
a2.show(c)
-
a2.show(d)
-
正常建立對象 b,涉及函數重載 show(),b 具有調用方法三 show(B obj) 和方法四 show(A obj) 的能力。同時 B 繼承自 A 是以擁有方法一 show(D obj) 和方法二 show(A obj) 其中方法二被方法四重寫覆寫。B b = new B();
由編譯器進行靜态綁定(前期綁定)方法三 show(B obj)。b.show(b)
-
b.show(c)
-
b.show(d)