Java 學習筆記(6)——繼承

以前說過了Java中面向對象的第一個特徵——封裝,這篇來說它的第二個特徵——繼承。通常在程序設計中,繼承是爲了減小重複代碼。
java

繼承的基本介紹

public class Child extends Parent{
    //TODO
}

使用繼承後,子類中就包含了父類全部內容的一份拷貝。c++

子類與父類重名成員的訪問。

重名屬性

當子類與父類中有重名的數據成員時,若是使用子類對象的引用進行調用,那麼優先調用子類的成員,若是子類成員中沒有找到那麼會向上查找找到父類成員。例如程序員

public class Parent{
    public int num = 10;
}

public class Child extends Parent{
    public int num = 20;

    public static void main(String[] args){
        Parent obj = new Child();
        System.out.println(obj.num); //輸出10
        Child c = new Child();
        System.out.println(c.num); //輸出20
    }
}

兩者一樣是建立的Child對象,那麼爲什麼一個是10,而一個是20 呢? 在C/C++中常常提到一個概念就是,指針自己存儲的是一個地址值,指針自己是沒有任何數據類型的,而指針指向的地址有,咱們在程序中定義的指針類型決定了代碼在訪問指針所指向的內存時是如何翻譯這段內存的。Java中雖說沒有開放操做內存的能力,可是引用自己也是一個指針。若是將父類類型的引用來保存子類對象的地址,那麼從引用類型來看,它只能看到父類的相關內容,因此這裏它將對象所在內存當作父類的類型,因此第一個打印語句訪問到的是父類的 num 成員。而第二個它能看到子類的整個內存,因此說它優先使用子類的 num 成員。函數

上面是第一種狀況,即在外部直接調用,若是經過方法來訪問呢this

public class Parent{
    private int num = 10;

    public int getNum(){
        return this.num;
    }
}

public class Child extends Parent{
    public int num = 20;

    public int getNum(){
        return this.num;
    }

    public static void main(String[] args){
        Parent obj = new Child();
        System.out.println(obj.getNum()); //輸出20
        Child c = new Child();
        System.out.println(c.getNum()); //輸出20
    }
}

第一條輸出語句實際上使用了Java中的多態特性,這個時候會調用子類的方法,而第二條直接調用子類方法,而在子類方法中經過this調用自身的 num 成員,因此這裏會輸出兩個20翻譯

無論哪一種狀況,在經過對象的引用調用對象成員的時候會根據引用類型來翻譯對應的內存,找到須要訪問的變量,若是子類中未找到則向上查找父類,若是父類也未找到,則會報錯。設計

下面來看看Java中繼承的內存圖來加深這個的理解指針

public class Parent{
    public int num = 10;

    public int getNum(){
        return this.num;
    }
}

public class Child extends Parent{
    public int num = 20;

    public int getNum(){
        return this.num;
    }

    public void show(){
        int num = 30;
        System.out.println(num); //30
        System.out.println(this.num); //20
        System.out.println(super.num); //10
    }

    public static void main(String[] args){
        Child obj = new Child();
        obj.Show();
        obj.getNum(); //20
    }
}

對象的內存分佈大體以下:
內存分配code

首先JVM加載程序的時候將類定義放到方法區中,此時方法區中有兩個類的定義,其中子類中有一個 [[class_super]] 標誌,用於標明它的父類是哪一個。而後建立main的棧並執行main函數,在main函數中建立了一個Child的對象,那麼JVM會根據方法區中的相關內容在堆中建立子類對象, 子類對象中包含一整份的父類的拷貝。而後調用Show方法,它首先根據方法區中的標識查找,在子類中找到了這個方法建立方法棧並調用。在方法中,定義了一個局部變量 num,當訪問 num這個變量時會首先在棧中查找,若是找到則訪問,若是沒有則在子類中查找,若是子類中也沒有,則會訪問父類。就這樣在棧中找到了num。接着訪問 this.num。 這個時候JVM會首先到子類中查找,找不到則會進一步查找父類。發如今子類中找到了。接着訪問 super.num,那麼JVM會主動去父類中查找。對象

show方法執行完了以後,JVM回收它的堆棧,接着調用getNum() 方法,查找方式與調用show的相同。後面的就再也不說了,與以前的相似。

重寫

向上面的例子中這樣的。當子類與父類的方法名、參數列表相同時,若是用子類對象進行調用,那麼會調用子類方法,這個時候父類的同名方法就被重寫了。注意:這裏並無強調返回值也同樣,其實這裏只須要返回值能正常的發生隱式轉換便可。這裏也沒有規定它的訪問權限,這裏只要求子類方法的訪問權限大於等於父類方法的訪問權限,也就是在同一條件下保證兩者都能訪問。雖然沒有這兩方面的限制,可是通常狀況下父類的方法與子類重寫的方法在形式上應該是徹底同樣的。

public class Parent{
    public int num = 10;

    public int getNum(int n){
        return this.num;
    }
}

public class Child extends Parent{
    public int num = 20;

    public int getNum(float f){
        return this.num;
    }
}

上面的代碼並無實現重寫的功能,可是若是程序員本身理解不到爲,認爲這是一個重寫,而且在代碼中按照重寫在使用,那麼編譯雖然不會報錯,可是在運行過程當中可能會出現問題,這個時候可使用 @Overwrite 註解,來告訴編譯器,這裏是一個重寫,若是編譯器判斷這個不是重寫,那麼編譯會報錯,這樣就將運行時錯誤提高到了編譯時,有注意bug的排除。

註解與註釋有什麼相同與區別呢:我認爲兩者都是用於對程序作一個說明,可是註釋是給人看的,註解是給機器看的。

重寫與重載有什麼區別呢?

  1. 重寫是發生在類的繼承關係中的,要求函數名、參數列表相同
  2. 重載並無規定使用的場合,要求函數名相同、而參數列表不一樣

既然說到重載,我想起來了java與c++中重載的一個區別

void sayHello(long f);
void sayHello(int);

根據上述函數的定義,java和c++中都造成了一個重載的關係,若是在C中使用代碼 sayHello(10) 來調用的時候,由於這裏的10既能夠理解爲long也能夠理解爲int,因此這裏發生了二義性,編譯會報錯,而java中 10 默認爲int,因此這裏會調用int參數的函數,而想要調用long型的參數,得寫成 sayHello(10L)。這算是兩者之間和有趣的一個現象。

構造函數

與C/C++中的相同,調用構造的時候會優先調用父類構造,像上面的代碼中,並無明顯的調用構造,在建立類的時候會隱式的調用構造,並在子類構造中隱式調用父類構造。可是一旦手工定義了構造,那麼編譯器將再也不額外提供構造。

public class Parent{
    Parent(int n){
        System.out.println(n);
    }
}

public class Child extends Parent{
    Child(){ //這個會編譯報錯
        System.out.println("Child");
    }
}

Child構造函數默認會調用父類的無參構造,可是因爲父類提供了構造,此時編譯器再也不提供默認的無參構造,因此這裏找不到父類的無參構造,報錯。這個代碼能夠進行以下的修改

public class Child extends Parent{
    Child(){ //這個會編譯報錯
        super(19);
        System.out.println("Child");
    }

    //或者
    Child(int n){
        //隱式調用super(n)
        System.out.println("Child");
    }
}

這裏使用super關鍵字顯示調用父類的帶參構造。或者定義一個帶參構造,編譯器會默認爲你添加上調用父類帶參構造的過程。

在Java中super關鍵字的做用主要有如下幾點:

  1. 在子類中訪問父類成員
  2. 在子類中調用父類構造函數,這種用法必須保證super在子類構造中是第一條被執行的語句,並且只能有惟一的一條

咱們說super表明父類,那麼this表明的就是類自己,那麼它有什麼具體的做用,又有哪些須要注意的點呢?

  1. this能夠訪問本類成員變量
  2. 在本類的成員方法中訪問另外一個成員方法
  3. 在本類構造方法中訪問另外一個構造

注意:第3中用法中,this關鍵字必須是構造方法執行的第一條語句,並且只能有惟一的一條。這條規則與super關鍵字的相同,那麼在構造中既有this又有super的時候該怎麼辦呢?答案是沒法這麼使用,Java中規定this和super關鍵字在構造函數中只能出現一個。

Java中的 繼承關係

在C++中,最讓人頭疼的是多繼承的菱形繼承關係,爲了防止二義性,Java禁止多繼承,只容許單繼承。可是java中運行多繼承,而且一個父類運行有多個子類。

在Java的多級繼承中若是出現同名的狀況,訪問時該怎麼辦呢?

原則仍然相同,根據new出來的對象和左側保存對象的引用類型來判斷,若是是父類類型,則訪問成員變量時只能訪問父類,若是是訪問方法則須要考慮多態。若是建立的是父類則只能訪問父類的成員變量和方法

相關文章
相關標籤/搜索