Java——多態淺析

前言

在面向對象程序設計語言中,多態是繼數據抽象和繼承以後的第三種基本特性。多態的含義是什麼,有什麼做用以及在Java中是怎麼實現的?下面將作介紹。html

什麼是多態

簡單點說就是「一個接口,多種實現」,不一樣類對同一操做體現出不一樣效果。設想有一個性質,一個引用變量所指向的確切類型和該引用變量調用的方法是哪一個類中的,這個兩個問題在編譯期間是不肯定的,在程序運行期間纔可肯定。因而,一份代碼就能夠適用於多個不一樣的類,只要這份代碼中有一個引用變量能夠指向這些不一樣的類的對象。在程序運行期間,就能夠動態選擇多個不一樣的對象或者多個不一樣的方法運行,這就是多態性java

也能夠簡單的使用Java核心卷中的一句話說:一個引用變量能夠指示多種實際類型的現象被稱爲多態。編程

要什麼樣的引用變量才能夠指向多種不一樣類的對象呢?那就須要是基類引用變量。這就涉及到向上轉型ui

向上轉型

簡單一句話就是:基類(父類)引用指向子類對象。取這個術語屬於也是有歷史緣由的,以傳統的類繼承圖的繪製方法爲基礎:將根置於頁面的頂端,而後逐漸向下。.net

爲何基類引用就能夠指向子類對象呢?設計

這能夠以生活中的一個例子來講,狗是對全部品種狗的統稱,具體的品種又有哈士奇,拉布拉多犬等。假設咱們在路上碰見了一隻不知道名字的狗(好比牧羊犬),咱們也許會說:那兒有一隻狗。此時,咱們就作了向上轉型,以狗指向了具體的牧羊犬。以範圍較大的基類引用去指向範圍小的子類。這是以生活中的例子解釋,在語言層面其實也能夠說明:子類是繼承基類而來,因此基類中的全部方法子類也有,能夠發送給基類的消息一樣也能夠發送給子類。使用基類引用也就能夠指向子類對象調用這些方法。code

可使用基類引用指向子類對象,那麼可不可使用子類引用指向基類對象呢?htm

是能夠的,這就叫作向下轉型,可是這存在風險。由於子類中可能會有新增方法,而基類中是沒有這些方法的,如果調用這些方法就會拋出ClassCastException異常。對象

介紹了什麼是多態,那麼就瞭解它的做用有什麼。blog

多態的做用

多態的一個好處就是能夠實現統一管理,須要注意父類不能調用子類特有的方法即父類中沒有的方法子類有的方法,若要調用須要向下轉型。

多態如何實現

Java中實現多態的三個要求爲:

  • 要有繼承關係

  • 方法要被重寫

  • 基類引用指向子類對象

能夠看一個簡單的多態例子

class Animal{
    public void eat() { System.out.println("吃東西"); }
}

class Dog extends Animal{
    public void eat() { System.out.println("吃狗糧");}
}

class Cat extends Animal{
    public void eat() { System.out.println("吃小魚乾");}
}

public class PloyTest {
    //使用父類引用指向子類對象 調用子類中被重寫的方法
    public static void printEatingFood(Animal a) { a.eat();}
    
    public static void main(String[] args) {
        printEatingFood(new Dog());
        printEatingFood(new Cat());
    }
}
/*
output:
吃狗糧
吃小魚乾
*/

當咱們傳入Dog對象時,a.eat()調用的是Dog類中被重寫的eat方法;傳入Cat對象時,a.eat()調用的是Cat類中被重寫的方法。程序在運行過程當中,依據咱們傳入的對象自動地爲咱們尋找到正確的方法調用。這就是多態技術的實現依據:動態綁定。

動態綁定

《Java編程思想》上這樣說:運行時父類引用根據其指向的對象,綁定到相應的對象方法上。

那麼這個過程的具體實現是怎樣的呢?《Java核心技術卷1》上是這樣解釋的解釋:

先是要清楚調用對象方法的過程:

  1. 搜索過程)編譯器查看對象的聲明類型和方法名。可能會有同名的重載方法。

如果調用x.f(param)

得到當前類和超類中爲public的名爲f的方法,即獲可能被調用的候選方法

  1. 匹配過程)而後,編譯器查看調用方法時提供的參數類型,與候選方法進行匹配,此過程也就是重載解析。

徹底匹配,則選擇調用該方法,這其中還會存在類型轉換,因此過程會比較複雜。最後如果沒有找到匹配的,編譯器則會報錯。

​ -------------------------------------------------------------------------------------------

靜態綁定:
若方法是private, static, final或者構造器,那麼編譯器會知道調用哪一個方法,這種方式爲靜態綁定。

動態綁定:
依賴於隱式參數的實際類型,在運行時才能夠肯定調用方法,則爲動態綁定。
當程序運行,而且採用動態綁定調用方法時,虛擬機會調用與x所引用對象的實際類型最合適的那個類的方法。而且爲了不每次搜索浪費時間,虛擬機會爲每一個類建立一個方法表。其中包含全部方法的簽名和實際調用的方法(包括繼承來方法)。

一些陷阱和建議

域和靜態方法

咱們須要注意只有普通方法調用才能夠是多態的,對域的訪問將在編譯時期進行解析。以下面這個例子:

class Super{
    public int field = 0;
    public int getField() { return field;}
}

class Sub extends Super{
    public int field = 1;
    public int getField() { return field;}
    public int getSuperField() { return super.getField();}
}

public class FieldAccess {
    public static void main(String[] args) {
        Super sup = new Sub();
        System.out.println("sup.field="+sup.field+" sup.getField()="+sup.getField());
        
        Sub sub = new Sub();
        System.out.println("sub.getField="+sub.field+" sub.getField()="+sub.getField()+
                " sub.getSuperField()="+sub.getSuperField());
    }
}
/*
output:
sup.field=0 sup.getField()=1
sub.getField=1 sub.getField()=1 sub.getSuperField()=0
*/

當使用父類Super的引用sup指向子類Sub類對象,輸出域,發現是父類的值。所以,域的訪問是編譯器解析,不是多態的。

若是某個方法是靜態的,它的行爲也不具備多態性

class StaticSuper{
    public static String staticGet() {
        return "Base staticGet()";
    }
    
    public String dynamicGet(){
        return "Base dynamicGet()";
    }
}

class StaticSub extends StaticSuper{
    public static String staticGet() {
        return "Derived staticGet()";
    }
    
    public String dynamicGet() {
        return "Derived dynamicGet()";
    }
}

public class OverloadingTest {
    public static void main(String[] args) {
        StaticSuper sup = new StaticSub();  //向上轉型
        System.out.println(sup.staticGet());
        System.out.println(sup.dynamicGet());
    }
}
/*
output:
Base staticGet()
Derived dynamicGet()
*/

由輸出能夠看出,靜態方法是不具備多態性的。靜態方法是與類,而非與單個的對象相關聯的。

小結

簡要介紹了對於多態的理解,其中存在的不足,但願各位看官不吝賜教。

參考:

《Java編程思想》第四版

《Java核心技術卷1》第九版

Java多態性理解,好處及精典實例:http://www.javashuo.com/article/p-qfgsjwub-hv.html

相關文章
相關標籤/搜索