多態就是指程式中定義的引用變數所指向的具體類型和通過該引用變數發出的方法調用在編譯時並不確定,而是在程式運行期間才確定。 即一個引用變數倒底會指向哪個類的實例對象,該引用變數發出的方法調用到底是哪個類中實現的方法,必須在由程式運行期間才能決定。 因為在程式運行時才確定具體的類,這樣,不用修改源程式代 ...
多態就是指程式中定義的引用變數所指向的具體類型和通過該引用變數發出的方法調用在編譯時並不確定,而是在程式運行期間才確定。
即一個引用變數倒底會指向哪個類的實例對象,該引用變數發出的方法調用到底是哪個類中實現的方法,必須在由程式運行期間才能決定。
因為在程式運行時才確定具體的類,這樣,不用修改源程式代碼,就可以讓引用變數綁定到各種不同的類實現上,從而導致該引用調用的具體方法隨之改變,即不修改程式代碼就可以改變程式運行時所綁定的具體代碼,讓程式可以選擇多個運行狀態,這就是多態性。
01、多態是什麼
Java的多態是什麼呢?其實就是一種能力——同一個行為具有不同的表現形式;換句話說就是,執行一段代碼,Java 在運行時能根據對象的不同產生不同的結果。
多態的前提條件有三個:
子類繼承父類
子類覆蓋父類的方法
父類引用指向子類對象
多態的一個簡單應用,來看程式清單1-1:
//子類繼承父類
public class Wangxiaoer extends Wanger {
public void write() { // 子類覆蓋父類方法
System.out.println("記住仇恨,表明我們要奮發圖強的心智");
}
public static void main(String[] args) {
// 父類引用指向子類對象
Wanger[] wangers = { new Wanger(), new Wangxiaoer() };
for (Wanger wanger : wangers) {
// 對象是王二的時候輸出:勿忘國恥
// 對象是王小二的時候輸出:記住仇恨,表明我們要奮發圖強的心智
wanger.write();
}
}
}
class Wanger {
public void write() {
System.out.println("勿忘國恥");
}
}
02、多態與後期綁定
現在,我們來思考一個問題:程式清單1-1在執行 wanger.write() 時,由於編譯器只有一個 Wanger 引用,它怎麼知道究竟該調用父類 Wanger 的 write() 方法,還是子類 Wangxiaoer 的 write() 方法呢?
答案是在運行時根據對象的類型進行後期綁定,編譯器在編譯階段並不知道對象的類型,但是Java的方法調用機制能找到正確的方法體,然後執行出正確的結果。
多態機制提供的一個重要的好處程式具有良好的擴展性。來看程式清單2-1:
//子類繼承父類
public class Wangxiaoer extends Wanger {
public void write() { // 子類覆蓋父類方法
System.out.println("記住仇恨,表明我們要奮發圖強的心智");
}
public void eat() {
System.out.println("我不喜歡讀書,我就喜歡吃");
}
public static void main(String[] args) {
// 父類引用指向子類對象
Wanger[] wangers = { new Wanger(), new Wangxiaoer() };
for (Wanger wanger : wangers) {
// 對象是王二的時候輸出:勿忘國恥
// 對象是王小二的時候輸出:記住仇恨,表明我們要奮發圖強的心智
wanger.write();
}
}
}
class Wanger {
public void write() {
System.out.println("勿忘國恥");
}
public void read() {
System.out.println("每周讀一本好書");
}
}
在程式清單 2-1 中,我們在 Wanger 類中增加了 read() 方法,在 Wangxiaoer 類中增加了eat()方法,但這絲毫不會影響到 write() 方法的調用。write() 方法忽略了周圍代碼發生的變化,依然正常運行。
多態的這個優秀的特性,讓我們在修改代碼的時候不必過於緊張,因為多態是一項讓程式員“將改變的與未改變的分離開來”的重要特性。
03、多態與構造器
在構造器中調用多態方法,會產生一個奇妙的結果,我們來看程式清單3-1:
public class Wangxiaosan extends Wangsan {
private int age = 3;
public Wangxiaosan(int age) {
this.age = age;
System.out.println("王小三的年齡:" + this.age);
}
public void write() { // 子類覆蓋父類方法
System.out.println("我小三上幼兒園的年齡是:" + this.age);
}
public static void main(String[] args) {
new Wangxiaosan(4);
// 上幼兒園之前
// 我小三上幼兒園的年齡是:0
// 上幼兒園之後
// 王小三的年齡:4
}
}
class Wangsan {
Wangsan () {
System.out.println("上幼兒園之前");
write();
System.out.println("上幼兒園之後");
}
public void write() {
System.out.println("老子上幼兒園的年齡是3歲半");
}
}
從輸出結果上看,是不是有點詫異?明明在創建 Wangxiaosan 對象的時候,年齡傳遞的是 4,但輸出結果既不是“老子上幼兒園的年齡是 3 歲半”,也不是“我小三上幼兒園的年齡是:4”。
為什麼?
因為在創建子類對象時,會先去調用父類的構造器,而父類構造器中又調用了被子類覆蓋的多態方法,由於父類並不清楚子類對象中的屬性值是什麼,於是把int類型的屬性暫時初始化為 0,然後再調用子類的構造器(子類構造器知道王小二的年齡是 4)。
04、多態與向下轉型
向下轉型是指將父類引用強轉為子類類型;這是不安全的,因為有的時候,父類引用指向的是父類對象,向下轉型就會拋出 ClassCastException,表示類型轉換失敗;但如果父類引用指向的是子類對象,那麼向下轉型就是成功的。
來看程式清單4-1:
public class Wangxiaosi extends Wangsi {
public void write() {
System.out.println("記住仇恨,表明我們要奮發圖強的心智");
}
public void eat() {
System.out.println("我不喜歡讀書,我就喜歡吃");
}
public static void main(String[] args) {
Wangsi[] wangsis = { new Wangsi(), new Wangxiaosi() };
// wangsis[1]能夠向下轉型
((Wangxiaosi) wangsis[1]).write();
// wangsis[0]不能向下轉型
((Wangxiaosi)wangsis[0]).write();
}
}
class Wangsi {
public void write() {
System.out.println("勿忘國恥");
}
public void read() {
System.out.println("每周讀一本好書");
}
}