在我刻板的印象裏,西遊記裏的那段孫悟空和二郎神的精彩對戰就能很好的解釋「多態」這個詞:一個孫悟空,能七十二變;一個二郎神,也能七十二變;他們均可以變成不一樣的形態,但只須要悄悄地喊一聲「變」。html
Java的多態是什麼呢?其實就是一種能力——同一個行爲具備不一樣的表現形式;換句話說就是,執行一段代碼,Java 在運行時能根據對象的不一樣產生不一樣的結果。和孫悟空和二郎神都只須要喊一聲「變」,而後就變了,而且每次變得還不同;一個道理。java
多態的前提條件有三個:程序員
子類繼承父類安全
子類覆蓋父類的方法微信
父類引用指向子類對象ide
多態的一個簡單應用,來看程序清單1-1:this
//子類繼承父類
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("勿忘國恥");
}
}
如今,咱們來思考一個問題:程序清單1-1在執行 wanger.write()
時,因爲編譯器只有一個 Wanger 引用,它怎麼知道究竟該調用父類 Wanger 的 write()
方法,仍是子類 Wangxiaoer 的 write()
方法呢?spa
答案是在運行時根據對象的類型進行後期綁定,編譯器在編譯階段並不知道對象的類型,可是Java的方法調用機制能找到正確的方法體,而後執行出正確的結果。代理
多態機制提供的一個重要的好處程序具備良好的擴展性。來看程序清單2-1:code
//子類繼承父類
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() 方法忽略了周圍代碼發生的變化,依然正常運行。這讓我想起了金庸《倚天屠龍記》裏九陽真經的口訣:「他強由他強,清風拂山崗;他橫由他橫,明月照大江。」
多態的這個優秀的特性,讓咱們在修改代碼的時候沒必要過於緊張,由於多態是一項讓程序員「將改變的與未改變的分離開來」的重要特性。
在構造器中調用多態方法,會產生一個奇妙的結果,咱們來看程序清單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)。
向下轉型是指將父類引用強轉爲子類類型;這是不安全的,由於有的時候,父類引用指向的是父類對象,向下轉型就會拋出 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("每週讀一本好書");
}
}
我喜歡把複雜的事情儘可能簡單化,把簡單的事情有趣化——多態是 Java 的三大特性之一,它原本須要長篇大論的介紹,但我以爲實在沒有必要,把關鍵的知識點提煉出來就足夠了。更重要的是,你要經過實踐去感知多態的優秀之處。
chenssy 大佬對多態下了一個很是經典的結論,咱們不妨大聲的朗讀幾遍:
多態就是指程序中定義的引用變量所指向的具體類型和經過該引用變量發出的方法調用在編譯時並不肯定,而是在程序運行期間才肯定;即一個引用變量倒底會指向哪一個類的實例對象,該引用變量發出的方法調用究竟是哪一個類中實現的方法,必須在由程序運行期間才能決定。由於在程序運行時才肯定具體的類,這樣,不用修改源程序代碼,就可讓引用變量綁定到各類不一樣的類實現上,從而致使該引用調用的具體方法隨之改變,即不修改程序代碼就能夠改變程序運行時所綁定的具體代碼,讓程序能夠選擇多個運行狀態,這就是多態性。
上一篇:Java代碼複用的三種經常使用方式:繼承、組合和代理
微信搜索「沉默王二」公衆號,關注後回覆「免費視頻」獲取 500G Java 高質量教學視頻(已分門別類)。