面向物件——多型與向上、向下轉型機制
前言
開頭先回憶一下,面向物件的三大特徵:封裝(資料抽象)、繼承、多型。為什麼多型排在最後一位,因為它是以前面兩個為前提的,尤其是繼承。
多型概念梳理
多型本質在於 同一種行為的多樣化表達 (這句話不禁讓我想起了基因的多樣性表達,這是現實世界物種多型的原因)。
對於某一種行為而言,它的多樣性體現在兩種可能:
1. 行為主體相同,行為受體不同——同樣是待客,為什麼他對別人溫柔對你凶?
2. 行為受體相同,行為主體不同——同樣是吃肉,人是煮熟吃,動物生吃。
這是我們在生活中的例子,現在我們遷移到程式設計中的多型!
我們是不是可以把一個方法稱為一種行為,他的呼叫者我們稱為“行為主體” ,他的引數我們稱為”行為受體”呢,而把函式體看作是行為的具體內容?
程式設計中,多型分為 編譯時多型 和 執行時多型,也稱為 前期繫結 和 動態繫結。
注:存在說法認為多型僅僅限定於執行時的多型,本人認為還是以“同一種行為的多樣化表達”的基準,說法是死的,思想是活的,大家也不用在這方面糾結。
編譯時多型:程式設計中的體現是方法過載,呼叫者是明確的,呼叫方法有多種,我們根據引數來確定呼叫哪個方法,從而體現多樣性。
執行時多型:根源於繼承之後的方法重寫,多個子類都繼承了相同父類,並且都對父類中某個普通方法進行了重寫,從而體現多樣性。
實驗
首先,先建立繼承關係如下:
轉型
能實現轉型,是執行時多型的必要條件之一。
編譯的機制:
根據你引用指定的型別去搜索你的方法是否存在,如果該引用型別中不存在呼叫方法,報錯。
執行時機制:
執行時,是根據引用所指的具體物件型別來呼叫方法。
當我們將父類中的study()方法註釋:
執行時:
從機器級角度分析這個問題:
編譯時根據物件引用確定該方法的符號引用名稱,執行時根據物件引用指向的物件進行符號引用的重定位。
向上轉型
如果你是一個大學生,那麼你肯定也是一名學生。
我們對你所屬的範圍進行了一次周延擴大,這就是一次向上轉型。
用處:
1.提高了程式的擴充性,不需要寫一些重複的程式碼。
2.增加程式碼簡潔性,可閱讀性。
假設我們有一個老師,他既教高中生,也教大學生,不同學生上課方式不同,正常那我們不是要進行方法過載為每個學生量身定製一個方法嗎?但是我們可以讓他們自適應:
結果:
向上轉型進階分析:
如果方法呼叫者和方法引數都是具有繼承關係的型別,這個向上轉型是怎麼轉的呢?
觀查測試樣例:
輸出:
A and B
當我們將A類中的show(B)註釋掉:
輸出:
B and A
規律總結:
先是呼叫者由下到上向上轉型查詢對應方法; 若沒找到,每次將呼叫引數向上轉型一次,再尋找對應方法。
向下轉型(注意點)
你是一個學生,但不一定是大學生。向下轉型,一定要注意型別的匹配。
為什麼又會有向下轉型?向上轉型的弊端在於被呼叫方法受到侷限,而當你又想呼叫子類獨有的方法,你就必須用 向下轉型 轉回去。
所以向下轉型一定是先有向上轉型作鋪墊的。
但是,這邊注意一點,你物件是什麼型別,你轉回去也應該是什麼型別。
人->動物->豬 ×
人->動物->人 √
編譯沒有報錯,但執行一定丟擲轉型異常。
正確做法:
牛刀小試
根據我們之前總結的規律,看看你的答案是否正確吧:
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 Demo {
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