面向對象的三大基本特徵:封裝,繼承和多態。
封裝:就是將類的內部實現細節封裝和數據封裝起來,不對外暴露實,向外部提供接口,來操作內部封裝的數據。
繼承:爲了提高代碼的重用性,如果一個類A和類B是IS-A關係,則可以使用繼承。
多態:指允許不同類的對象對同一消息做出響應。即同一消息可以根據發送對象的不同而採用多種不同的行爲方式。(發送消息就是函數調用)。
作用:可以根據實現類的不同而實現不同的功能,以消除類型之間的耦合關係。
現實生活中的多態
比方說按下 F1 鍵這個動作,如果當前在 Flash 界面下彈出的就是 AS 3 的幫助文檔;如果當前在 Word 下彈出的就是 Word 幫助;在 Windows 下彈出的就是 Windows 幫助和支持。同一個事件發生在不同的對象上會產生不同的結果。
下面來看這樣一個例子
public class Person {
public void fun(){
System.out.println("person的fun()");
display();
}
public void display(){
System.out.print("person");
}
}
public class Man extends Person {
public void fun(int a){
System.out.println("Man的fun1(int a)");
display();
}
@Override
public void display() {
System.out.println("Man");
}
public static void main(String[] args){
Person p = new Man();
p.fun();
//p.fun(1); 編譯錯誤,因爲Person類中並沒有fun(int)方法
}
}
Output:
person的fun()
Man
從這個例子可以看出,p首先調用person類的fun()方法,然後在方法fun()中調用的是Man類的display()方法。
這是爲何呢?原因其實很簡單,p首先調用fun()方法,而這個方法子類Man中並未重寫fun()方法(需要注意的是Man中只是對fun()方法重載了,並未重寫)然後在fun()方法中調用了display方法,發現子類Man重寫了display方法於是調用Man中的display方法。
總結:
指向子類的父類引用由於向上轉型了,它只能訪問父類中擁有的方法和屬性,而對於子類中存在而父類中不存在的方法,該引用是不能使用的,儘管是重載該方法。若子類重寫了父類中的某些方法,在調用該些方法的時候,必定是使用子類中定義的這些方法(動態連接、動態調用)。
關於動態綁定的細節
對於面向對象而已,多態分爲編譯時多態和運行時多態。其中編輯時多態是靜態的,主要是指方法的重載,它是根據參數列表的不同來區分不同的函數,通過編輯之後會變成兩個不同的函數,在運行時談不上多態。而運行時多態是動態的,它是通過動態綁定來實現的,也就是我們所說的多態性。
多態的實現
多態的實現條件
1.繼承:在多態中必須存在有繼承關係的子類和父類。
2.重寫:子類對父類中某些方法進行重新定義,在調用這些方法時就會調用子類的方法。
3.父類引用指向子類對象。
多態的原則
Java中引用變量實際調用的方法不是由引用變量的類型決定的,而是有變量指向對象的實際類型決定的。
多態的實現方式
繼承和接口
繼承實現多態
public class Person {
public void display(){
System.out.print("person");
}
}
public class Man extends Person {
@Override
public void display() {
System.out.println("Man");
}
}
public class Woman extends Person {
@Override
public void display() {
System.out.println("Woman");
}
public static void main(String[] args){
Person[] persons = new Person[2];
persons[0] = new Man();
persons[1] = new Woman();
for (int i =0;i<persons.length;i++){
persons[i].display();
}
}
}
Output:
Man
Woman
從這個例子可以看出基於繼承的實現機制主要表現在父類和繼承該父類的一個或多個子類對某些方法的重寫,多個子類對同一方法的重寫可以表現出不同的行爲。
接口實現多態
此處有兩點不同:
1.因爲接口方法都是抽象方法,所以具體的實現都是有子類去完成的。所以,在編碼的時候,接口必須執行一個具體的實現類,調用實現類中的方法。
2.因爲類的繼承是但繼承,而一個類可以實現多個接口,這就爲編程帶來了靈活性。
關於多態的經典的例子
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{}
class E
{
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(a1.show(b)); //①
System.out.println(a1.show(c)); //②
System.out.println(a1.show(d)); //③
System.out.println(a2.show(b)); //④
System.out.println(a2.show(c)); //⑤
System.out.println(a2.show(d)); // ⑥
System.out.println(b.show(b)); //⑦
System.out.println(b.show(c)); //⑧
System.out.println(b.show(d)); //⑨
}
}
Output:
① A and A
② A and A
③ A and D
④ B and A
⑤ B and A
⑥ A and D
⑦ B and B
⑧ B and B
⑨ A and D
在分析這個之前,我們先來看一個原則:
其實在繼承鏈中對象方法的調用存在一個優先級:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)
下面我們來看一下上面這個例子的繼承關係圖
對於①、②、③來說很簡單
但是對於下面的輸出結果可能就有人搞不清楚了,下面我們來具體分析一下。
④、A類引用a2指向B類對象的實例,所以現在A中尋找,與實際參數類型匹配的方法,發現並沒有,所以,就將b向上轉型爲A再去查找,發現public String show(A obj)方法,接着調用。
⑤、 A類引用a2指向B類對象的實例,所以現在A中尋找,與之匹配的參數,發現並沒有,所以,就將b向上轉型爲A再去查找,但是在這裏,B類中重寫了public String show(A obj)所以調用B類對象中的。
⑥ 、直接在A類中查到了與之實際參數類型匹配的方法,然後直接調用。
⑦ 、B類引用b創建一個B類對象,直接查找B類中的方法,發現有與實際參數類型匹配的方法,直接調用即可。
⑧ 、B類引用b創建一個B類對象,直接查找B類中的方法,並未發現有與實際參數類型匹配的方法,直接去A類中尋找,也未找到,所以將實際參數類型C類類型轉換爲B類類型,在B類中找到了對應的方法,直接調用即可。
⑨、B類引用b創建一個B類對象,在B類中查找,並未找到,然後再去A類中查找。