隱藏和覆蓋的區別和用法


講隱藏和覆蓋之前先看兩個概念:靜態類型動態類型

任何一個引用變量都有兩個類型:一個叫靜態類型,也就是定義該引用變量的類型;另一個叫動態類型,也就是該引用實際指向的對象類型。

比如對於兩個類A和類B,有:A a=new B();

那么,引用a的靜態類型就是A,動態類型就是B。

java中引用的靜態類型在編譯的時候就可以確認,但是編譯器無法得知這個引用的動態類型;只有當程序運行時,通過RTTI就可以檢查出引用的動態類型。

再介紹一下,java中綁定的概念:對於一個程序,可以有很多的方法。這些方法的名稱、參數類型和參數數量都可能相同或者不同,那么在調用一個方法的時候,如何將一個方法和調用該方法的主體關聯起來,這就是綁定。java中的綁定分為靜態綁定和動態綁定。

靜態綁定:所有依賴於靜態類型來將某方法和該方法所在的類關聯起來的動作都是靜態綁定。因為靜態綁定在程序運行前發生,所有又叫前期綁定。

動態綁定:所有依賴於動態類型來將某方法和該方法所在的類關聯起來的動作都是動態綁定。因為動態綁定是在程序運行時,通過RTTI實現,所以又叫后期綁定。

舉例:假如有一個父類Father和一個子類Son,子類重寫了父類中的某個方法method()。有以下語句:

Father father=new Son();

father.method();

對於這個例子,靜態綁定的過程是:java文件編譯時,編譯器檢查出引用father的靜態類型是Father類,由於將method()方法和父類Father關聯起來。也就是說,程序運行前編譯器是無法檢查出引用father的動態類型的,所以會直接調用靜態類型中對應的方法。

而動態綁定的過程是:當這個java程序運行起來了,RTTI檢查出引用father的動態類型是Son類時,會將method()方法和子類Son關聯起來,也就是決定調用動態類型Son類中的method()方法。具體過程為:①JVM提取對象的實際類型的方法表;②JVM搜索方法簽名;③調用方法。

另外,要補充的是:java中類的屬性也都是靜態綁定的,static、final、private(視為final,也是無法被覆蓋的)是靜態綁定,其余方法都是動態綁定。這是因為靜態綁定是有很多的好處,它可以讓我們在編譯期就發現程序中的錯誤,而不是在運行期。這樣就可以提高程序的運行效率!而對方法采取動態綁定是為了實現多態。

多態的含義是,“一個接口,多種形式”。通過繼承,子類對象隱藏地持有一個引用名為super的父類對象,所以子類對象可以訪問父類的屬性和方法。又因為向上轉型的安全性和自動轉換的特點,所以這導致任何一個類的對象既可以視為該類自身的對象,又可以視為該類的基類的對象;一個基類和它的所有子類的對象都屬於同一類型。這個特點表現的是不同類型之間的耦合關系,有很明顯的優點:同一份代碼可以無差別地運行在這些不同類型上(屬於同一個基類):比如我只要把某方法的參數設為(object o),那么傳遞給該方法任意對象作為實參都可以調用該方法。而多態是為了消除類型之間的耦合關系,體現基類同一個接口(方法)被子類繼承后表現的差異性。這種差異性需要通過重寫父類方法來實現(不是重載,是對父類方法的重新解釋以適應子類自身的需求)。

重寫注意事項:①final修飾的父類方法不可以被重寫 ②private視為final,但是又有所不同。你可以在子類中重寫父類的private方法,但是實際上應當屬於你新創建的一個方法,畢竟你壓根無法訪問該private方法。不過一般建議,不要重寫父類的private方法。③方法重寫時,可見性(訪問控制權限)不能降低。比如public方法重寫后設置為protected是不允許的!

考慮一個例子:父類Father一個方法move() 被子類繼承后,子類即時重寫了該方法,修改了其方法體的內容,有了屬於自己的move()方法。但是當子類向上轉型后,Father f=new Son(); f.move()你猜調用的是哪個方法?如果只有靜態綁定,調用的必然是父類的move()方法,這就體現不了多態了!所以,必須引入動態綁定機制,正確的調用子類的move()方法。

所以,多態需要的條件是:①繼承(復用類的重要方式) ②重寫(沒有重寫則一成不變,沒有差異性)③動態綁定(消除了同一基類的不同類型的耦合性)。

----------------------------------------------------------------------------------------------------------------分割線------------------------------------------------------------------------------------------------------------------

下面來說一下,java中的隱藏和覆蓋的概念。我們知道,當子類繼承父類時,除了繼承父類所有的成員變量和成員方法之外,還可以聲明自己的成員變量和成員方法。那么,如果父類和子類的成員變量和方法同名會發生什么?假設有一個父類Father和一個子類Son。父類有一個成員變量a=0;有一個靜態成員變量b=0;有一個成員方法a,輸出0;有一個靜態成員方法b,輸出0。子類分別重寫這些變量和方法,只是修改變量的值和方法的輸出,全部改為1.   我們再聲明一個靜態類型是父類,動態類型是子類的引用:

Father father=new Son();

通過這個引用訪問子類的變量和調用子類的方法,那么,會有以下結論:

1、所有的成員變量(不管是靜態還是非靜態)都只進行靜態綁定,所以JVM的綁定結果會是:直接訪問靜態類型中的成員變量,也就是父類的成員變量,輸出0.

2、對於靜態方法,也是只進行靜態綁定,所以JVM會通過引用的靜態類型,也就是Father類,來進行綁定,結果為:直接調用父類中的靜態方法,輸出0.

3、對於非靜態方法,會進行動態綁定,JVM檢查出引用father的動態類型,也就是Son類,綁定結果為:調用子類中的靜態方法,輸出1.

對於1和2這兩種情況,子類繼承父類后,父類的屬性和靜態方法並沒有被子類抹去,通過相應的引用可以訪問的到。但是在子類中不能顯示地看到,這種情況就稱為隱藏。

而對於3這種情況,子類繼承父類后,父類的非靜態方法被子類重寫后覆蓋上去,通過相應的引用也訪問不到了(除非創建父類的對象來調用)。這種情況稱為覆蓋。

總結一下,就是,子類繼承父類后:

父類的成員變量只會被隱藏,而且支持交叉隱藏(比如靜態變量被非靜態變量隱藏)。父類的靜態方法只會被靜態方法隱藏,不支持交叉隱藏。父類的非靜態方法會被覆蓋,但是不能交叉覆蓋。

代碼測試如下:

package test;
/* 隱藏和覆蓋的區別 */
public class HideAndCover {
    public static void main(String[] args) {
        Father father=new Son();
        System.out.println(father.a);
        System.out.println(father.b);
        father.c();
        father.d();
    }

}
//聲明父類
class Father{
    static int a=0;
    int b=0;
    void c() {
        System.out.println(0);
    }
    static void d() {
        System.out.println(0);
    }
}
//聲明子類
class Son extends Father{
    static int a=1;
    int b=1;
    void c() {
        System.out.println(1);
    }
    static void d() {
        System.out.println(1);
    }
}

運行結果為:

0

0

1

0

總的來說:繼承體現了同一個基類的不同子類之間的耦合性,而多態則是消除這種耦合體現這些類型之間的對於方法的差異性。前期綁定導致的后果就是隱藏;后期綁定導致的后果就是覆蓋,繼而實現多態。屬性和靜態方法(還有final、private)都是靜態綁定,其余的實例方法是動態綁定。所以,只有實例方法可以體現多態性,域(屬性)和靜態方法體現不出多態。

——over。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM