1、面向對象的三大基本特徵:封裝、繼承、多態
2、多態是指:父類引用指向子類對象,在執行期間判斷所引用對象的實際類型,根據其實際的類型調用其相應的方法。
3、實現多態的三要素:繼承 重寫 父類引用指向子類對象
多態可以說是建立在繼承的基礎上的,所以我們先來理解一下繼承。
Java中繼承的概念是:繼承是指一個對象直接使用另一對象的屬性和方法(此處引用百度百科)。也就是當一個子類繼承了一個父類時,子類也同時繼承了父類的非靜態屬性和方法(可以繼承父類的private方法、變量、只是無法通過子類對象修改),那麼意思就是子類對象可以調用父類的方法並且可以訪問父類的變量(只能改變非私有變量)。
如下例子:
class Father{
public String name;
private String id_num;
public static int age;
public void say(){
System.out.println("父類說...");
}
}
class Son extends Father{
public void say(){
System.out.println("子類說...");
}
}
public class Main{
public static void main(String[] argv){
Son son = new Son();
//可以正確訪問並修改
son.name = "zdy";
//可以正確訪問但不能修改
son.id_num = 04152017; //此時編譯器會報錯,因爲子類只能訪問不能修改父類的private屬性
//無法從父類繼承static的屬性和方法(因爲靜態屬性和方法是初始化的時候就已經存在的)
//會執行子類的say方法,因爲我們對父類的方法進行了重寫(多態)
son.say(); //會輸出“子類說...”
}
}
Java中多態的理解
簡單瞭解完了Java中的繼承,我們就來看看多態吧,什麼是多態?從字面意思上理解多態就是有多種形態。在編程中我們也可以將多態理解爲有多種形態,具體是如何體現多種形態的呢?一般通過兩種方式實現多態:覆蓋(重寫)、重載。
這兩種方式有什麼區別又有什麼聯繫?
首先區別:
1.覆蓋要求子類中的某一方法名和參數和父類中的某一方法名和參數完全一致。而重載只要求方法名一樣而參數名必須不 一致
2.覆蓋要求子類中的方法與父類中被覆蓋的方法返回類型必須一致。而重載可以不一致(參數列表必須不一致)。
3.覆蓋要求子類覆蓋父類的方法中不能有新拋出的異常類型。而重載允許有新拋出的異常類型。
4.覆蓋要求子類覆蓋父類的方法的訪問權限必須比父類的對應方法大(public > protected > default > private)。而重載 則沒有這種限制。
聯繫:
1.都是Java中多態性的典型體現。
2.都基於Java的繼承機制。
理解了上面的基本概念後我們應該對多態有了一個初步的認識,但是多態真正的概念是:指程序中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在編程時並不確定,而是在程序運行期間才確定,即一個引用變量倒底會指向哪個類的實例對象,該引用變量發出的方法調用到底是哪個類中實現的方法,必須在由程序運行期間才能決定。因爲在程序運行時才確定具體的類,這樣,不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類實現上,從而導致該引用調用的具體方法隨之改變,即不修改程序代碼就可以改變程序運行時所綁定的具體代碼,讓程序可以選擇多個運行狀態,這就是多態性。
爲了更好地理解多態,我們引入一個概念:“向上轉型”。我們先來看如下示例代碼:
public class Main{
public static void main(String[] argv){
Father f = new Son();
f.say();
}
}
Father類和Son類仍沿用上面示例的類。這種寫法可能很多初學者都理解不了了,本應指向Father類的對象怎麼能指向Son類呢?這時就是向上轉型了。Son類的對象向上轉型爲Father類的對象,然後被Father類的變量引用了。這就是我們所說的運行時多態,特點就是:父類對象在運行時可以表現子類特徵。所以這裏調用say方法時看子類中是否重寫了say方法,如果重寫了say方法那麼調用子類中重寫的say方法,反之調用父類中的say方法。
看一個很經典的案例:
class A{
public void say(A obj){
System.out.println("A and A");
}
public void say(D obj){
System.out.println("A and D");
}
}
class B extends A{
public void say(B obj){
System.out.println("B and B");
}
public void say(A obj){
System.out.println("B and A");
}
}
class C extends B{
}
class D extends D{
}
public class Main{
public static void main(String[] args){
A a = new B();
B b = new B();
a.say(b);
}
}
我們分析一下這段代碼的運行結果,首先我們創建了一個A類的變量a指向B類的對象,然後正常實例化一個B類的引用變量b,最後用a調用say方法將b作爲參數傳進去。用a調用say方法,由於對象a在運行時體現的是子類的特徵所以我們在子類中找say方法,在看傳入的參數,是一個B類的實例對象,看到這裏很多人會認爲答案是“B and B”(包括幾天前的我),那麼就錯了,首先我們定義的是一個A類的引用變量,這個引用變量能調用的方法只有父類的方法和子類中重寫父類的方法,say(B obj)方法明顯是重載父類的方法,所以我們是不能調用的,那爲什麼會調用say(A obj)方法呢?首先我們知道,運行時多態是會向上轉型的,B類的對象實例會轉爲A的對象實例,那麼參數就滿足了,由於A類中的say(A obj)方法被子類B重寫所以只會調用子類的方法,所以,最終的輸出結果是“B and A”。
實際上多態中有一個方法調用的優先級:this.fun(obj) > super.fun(obj) > this.fun(super(obj)) > super.fun(super(obj))
我們可以從內存的角度來解釋一下,首先A類的引用變量可以指向成員變量和成員方法的數目是不能變的,所以多態中父類的引用變量只能引用父類與子類共有的部分(總數不變),共有的部分指父類的所有屬性和方法,如果子類中有重寫父類的屬性和方法則包括重寫的屬性和方法,無法引用子類中特有的屬性和方法。