【Java基礎】-【面向對象】-多態

在面向對象程序設計語言中,多態是三種基本特性之一(其他兩種分別是 抽象、繼承)。

多態又稱動態綁定、後期綁定、運行時綁定。

我們先來看一個多態的例子:

public class Test {
public static void main(String[] args) {
play(new Cat());
play(new Dog());
}
public static void play(Animal animal) {
animal.enjoy();
}
}

class Animal {
public Animal() {}
public void enjoy() {
System.out.println("叫聲...");
}
}

class Cat extends Animal{
public Cat() {}
@Override
public void enjoy() {
System.out.println("喵...");
}
}

class Dog extends Animal {
public Dog() {}
@Override
public void enjoy() {
System.out.println("汪...");
}
}

我們可以看到,在main方法裏面我們調用的是play()方法,這個方法的參數是一個Animal類型的對象,但是實際上我們傳入的是Animal的子類的對象,而程序運行實際上調用的就是子類的enjoy()方法。像這種 父類引用指向子類對象的 現象就是多態。

多態機制可以有效的消除數據類型之間的耦合度增加代碼的可擴展性與於可讀性改善代碼的組織結構,爲什麼這麼說呢,因爲在你使用基類接口實現多態機制後,我們只需要編寫與基類相關聯的代碼就可以了,無需關心具體實現的子類以及以後有可能增加的子類,並且這些代碼對於所有子類都是可以正確運行的,因爲最終程序調用的是你傳入的子類所重寫的方法。

接下來我們開看看Java在內部是如何實現這種機制的吧,首先你需要知道什麼是“綁定”,所謂綁定,就是 將一個‘方法調用’和一個‘方法主體’關聯起來,綁定分爲兩種:

在程序執行前進行綁定,通常是由編譯器和連接程序實現,叫做前期綁定,對於上面的程序,當編譯器只有兩個Animal類型的引用的時候,它無法知道調用的是哪個play()方法。此時,就需要後期綁定,所謂後期綁定,也稱動態綁定,它的含義就是在運行過程中根據對象的類型進行方法綁定,後期綁定的機制可以在運行的時候判斷對象的類型,從而調用恰當的方法,也就是說,編譯器一直不知道對象的實際類型,他所掌握的只是一個基類的引用。

多態通過分離“做什麼”與“怎麼做”,來將接口與實現分離開,有了多態,就可以很好的提高程序的擴展性、降低類型之間的耦合度,在主業務邏輯代碼中(main),我們根本不用考慮傳入play()方法中的是什麼,只要它是Animal類型,那麼就可以正確運行,在以後業務中如果增加一些新的Animal子類型,也無需修改現有的基類方法!

多態的三個重要特徵:

1. 繼承(或接口實現)

2. 重寫(動態綁定子類方法)

3. 父類引用指向子類對象(向上轉型)

多態的缺陷:

1. 對於“假覆蓋”:

基類中的private方法是不對子類可見的,只有非private方法才能被覆蓋,子類中的“覆蓋”private方法,實際上是一個全新的、與基類沒有關係的方法,此時如果使用多態進行調用,編譯器不會報錯,但是也不是我們所期望的結果,因爲你需要知道,對於子類擴展方法,它們不會暴露在基類的引用視野中

2. 訪問“域”和靜態方法

我們先來看這個例子:

public class Test {
    public static void main(String[] args) {
Super s = new Sub();
System.out.println(s.getN());
System.out.println(s.n);
}
}
class Super {
public int n = 0;

public int getN() {
return n;
}
}
class Sub extends Super {
public int n = 1;

public int getN() {
return n;
}
public int getSuperN() {
return super.n;
}
}
我們看上面的程序,按照多態機制,我們很容易想象對於子類對象s它所調用的方法在執行期會自動轉成自己的方法,而不是父類引用的方法,但是調用域(成員變量)的時候,則不存在多態,因此s.n所調用的依然是父類中的變量,程序執行結果是:1  0,即:只有普通的方法調用可以使多態的

另外,靜態方法是不存在多態的,因爲多態本質是基於解決降低型耦合度的一種機制,而靜態的方法不是針對於某一個對象,它屬於整個類的,因此,靜態方法不存在多態。

3. 構造器內部的多態
首先,這其實並不屬於一種缺陷,這是一個很有意思的程序,來自於thinking in java :

public class Test{
public static void main(String[] args) {
new Teacher(10);
}
}

class People {
public People() {
System.out.println("before people draw...");
draw();
System.out.println("after people draw...");
}
void draw() {
System.out.println("people draw...");
}
}

class Teacher extends People {
private int r = 1;
public Teacher(int n) {
super();
r = n;
System.out.println("Teacher ... r = "+r);
}
public void draw() {
System.out.println("Teacher draw ... r = "+r);
}
}

 

 

 

我們可以看到Teacher類重寫了People類的draw方法,而在父類構造器中調用了draw方法,由於實例化子類必須首先調用父類構造器,因此,隱式的存在了多態現象,我們期望的結果就是:

1>. 調用基類構造器:

1.1>. 輸出before people draw...

1.2>. 多態:動態綁定到子類重寫的draw方法:Teacher draw ... r = 1

1.3>. 輸出after people draw...

2>. 初始化自己的成員

2.1>.賦值

2.2>. 輸出Teacher ... r = 100

但是,實際當我們運行程序的時候,其結果卻是:

before people draw...
Teacher draw ... r = 0
after people draw...
Teacher ... r = 10

區別在於構建父類對象的時候子類對象的成員變量r的值是0,這就涉及到了初始化的順序:

首先:在其他任何事物初始化之前,對象的存儲空間初始化成二進制0

隨後:逐步向下調用基類構造器,上述例子則是調用People構造器,由於上一步的原因,多態所調用的draw方法中的r初始化成了0

然後:按照成員聲明順序進行初始化

最後:執行子類的構造器主體

對於構造器安全的做法:在構造器內,爲以安全調用的那些方法是基類中的final方法(當然包括peivate方法,private方法是隱式的final),因爲這些方法不能被覆蓋!

 

多態,是一項讓程序員“將改變的事物與未變的事物分離開”的重要技術!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章