java 繼承

爲什麼需要繼承?

//大學生類
public class UnStudent{
    //定義屬性
    String name;
    Int ID;
    Int age;
    //定義方法
    public void study(){
        System.out.println("學習!");
    }
    public void sleep(){
        System.out.println("睡覺!");
    }
}
//高中生
public class SenoirStudent{
    //定義屬性
    String name;
    Int ID;
    Int age;
    //定義方法
    public void study(){
        System.out.println("學習!");
    }
    public void sleep(){
        System.out.println("睡覺!");
    }
}

從這裏我們可以看出來,高中生類和大學生類擁有相同的屬性和方法,如果此時再多出一個初中生類,和一個小學生類呢?難道我們還要再去拷貝三份代碼嗎?顯然我們是拒絕的(因爲程序員都比較懶),此時我們想最大限度的利用重複的代碼。於是很容易就想到了,能不能寫一個模板,讓其他要實現的類具體化一下它呢?

引子

   “複用代碼是Java衆多引人注目的功能之一。但要想成爲極具革命性的語言,僅僅能夠複製代碼並對加以改變是不夠的,它還必須能夠做更多的事情。在這句話中最引人注目的是“複用代碼”,儘可能的複用代碼使我們程序員一直在追求的,現在我來介紹一種複用代碼的方式,也是java三大特性之一---繼承。”
                                                                     --《Thanking in java》

繼承的概念

繼承就是子類繼承父類的特徵和行爲,使得子類對象(實例)具有父類的實例域和方法,或子類從父類繼承方法,使得子類具有父類相同的行爲。

生活中的繼承
這裏寫圖片描述

想象我們的生活中無處不存在這繼承。動物和植物都是屬於生物的範疇,而狗和鳥都是屬於動物的範疇。所以說動物和植物都是繼承生物,而狗和鳥都是繼承動物。
此時,想必大家都明白了吧,對於被繼承類來說更加的通用一點,而對於繼承類來說更加具體一點。

IS-A關係
如果有兩個對象A和B,若可以描述爲“A是B”,則可以表示A繼承B,其中B是被繼承者稱之爲父類或者超類,A是繼承者稱之爲子類或者派生類(很容易理解說動物是生物)。


繼承關鍵字

繼承可以使用 extends 和 implements 這兩個關鍵字來實現繼承,而且所有的類都是繼承於 java.lang.Object,當一個類沒有繼承的兩個關鍵字,則默認繼承object(這個類在 java.lang 包中,所以不需要 import)祖先類。

extends關鍵字
java中類與類之間的繼承都是單繼承,也就是說一個類只能繼承另一個類(注意與c++的區別,c++中一個類可以繼承多個類)

public class A extends B{
}

implements關鍵字
使用 implements 關鍵字可以變相的使java具有多繼承的特性,使用範圍爲類繼承接口的情況,可以同時繼承多個接口(接口跟接口之間採用逗號分隔)。

interface A{
}
interface B{
}
public interface C implements A,B{
}

此時再來看看上面的問題

//公共的學生類
public class Student{  
    //定義屬性  
    String name;  
    Int studentId;  
    Int age;  
    //定義方法  
    public void study(){  
        System.out.println("學習!");  
    }  
    public void sleep(){  
        System.out.println("睡覺!");  
    }  
}  
public class UnStudent{
}
public class SenoirStudent{
}

此時可以看出來,代碼簡潔來許多,同時我們接着向擴充初中生和小學生類也非常容易。

繼承的特性

  • 子類擁有父類非private的屬性,方法。
  • 子類可以擁有自己的屬性和方法,即子類可以對父類進行擴展。
  • 子類可以用自己的方式實現父類的方法。
  • java的繼承是單繼承,但是可以多重繼承,單繼承就是一個子類只能繼承一個父類,多重繼承就是,例如A類繼承B類,B類繼承C類,所以按照關係就是C類是B類的父類,B類是A類的父類,這是java繼承區別於C++繼承的一個特性。
  • 提高了類之間的耦合性(繼承的缺點,耦合度高就會造成代碼之間的聯繫)。

使用繼承的時候有下面幾個值得注意的問題

一 構造器
通過前面我們知道子類可以繼承父類的屬性和方法,除了那些private的外還有一樣是子類繼承不了的—構造器。對於構造器而言,它只能夠被調用,而不能被繼承。但是父類的構造器帶有參數的(沒有無參構造器的情況下),則必須在子類的構造器中顯式地通過super關鍵字調用父類的構造器並配以適當的參數列表。
如果父類有無參構造器,則在子類的構造器中用super調用父類構造器不是必須的,如果沒有使用super關鍵字,系統會自動調用父類的無參構造器。
對於子類而已,其構造器的正確初始化是非常重要的,構造器的初始化順序有如下規則:
先初始化父類的構造器,在初始化子類的構造器。看下面一個例子.

public class SuperClass {

    private int n;  
    public SuperClass(int n) {
        this.n = n;
        System.out.println("SuperClass Constructor");
    }
}

public class SubClass extends SuperClass{
    public SubClass(int n) {
        //注意此處不顯示調用父類的構造器編譯器會報錯bride
        //如果不想顯示調用可以在父類中加一個無參構造器
        super(n);
        System.out.println("SubClass Constructor");
    }

    public static void main(String[] args) {
        SubClass subClass = new SubClass(100);
    }
}

Ouput:
SuperClass Constructor
SubClass Constructor

二 protected權限修飾符
當我們使用private關鍵字的時候,對於封裝來說確實很好,因爲它對外隱藏了自己。但是我們有些時候可能會有這樣的需求:某些事物儘可能地對這個世界隱藏,但是對於自己的子類來說是可以訪問的。這個時候就使用到了protected關鍵字。
對於protected而言,它指明就類用戶而言,他是private,但是對於任何繼承與此類的子類而言或者其他任何位於同一個包的類而言,他卻是可以訪問的。(不在當前類的包下,當前類對象不可以調用protected訪問權限的方法和屬性,但是子類卻可以調用protected修飾的方法和屬性,即使子類和父類不在同一個包下)。

public class Student {

    protected String name;
    protected String getName() {
        return name;
    }
}

public class SeniorStudent extends Student {

    public SeniorStudent() {
        name = "小明";
        System.out.println("my name is:"+getName());
    }

    public static void main(String[] args) {
        SeniorStudent stu = new SeniorStudent();
    }
}

Output:
my name is:小明

注意:只有當我們需要子類可以訪問父類的屬性或者方法的時候才使用protected修飾符,切不可濫用(因爲會破壞面向對象封裝的特性)。

三 向上轉型
因爲繼承是is-a的相互關係,從一個簡單的例子來看,鳥繼承動物,所以我們可以說鳥是動物。我們說鳥是動物就是向上轉型 。

public class Animal {

    public void display(){
        System.out.println("Animal");
    }

    public static void show(Animal animal){
        animal.display();
    }
}

public class Bird extends Animal {

    public static void main(String[] args) {
        Bird bird = new Bird();
        Animal.show(bird);
    }
}

Output:
Animal

我們可以看到我們傳入show方法中去的是一個Bird,而我們的形式參數是一個Animal,所以我們就可以把Bird看作一個Animal。

將子類轉換成父類,在繼承關係上面是向上移動的,所以一般稱之爲向上轉型。由於向上轉型是從一個叫專用類型向較通用類型轉換,所以它總是安全的,唯一發生變化的可能就是屬性和方法的丟失。這就是爲什麼編譯器在“未曾明確表示轉型”活“未曾指定特殊標記”的情況下,仍然允許向上轉型的原因。


關於繼承類的對象創建過程和內存分析

以前面提到的學生爲例子:

//公共的學生類
public class Student{  
    //定義屬性  
    String name;  
    Int studentId;  
    Int age;  
    public Student(){
        super();
        System.out.println("Student");
    }
    //定義方法  
    public void study(){  
        System.out.println("學習!");  
    }  
    public void sleep(){  
        System.out.println("睡覺!");  
    }  
}  

public class SenoirStudent{
    //班級編號
    private String classNumber;
    public SenoirStudent(){
        super();
        System.out.println("SenoirStudent");
    }
    public void nationExam(){  
        System.out.println("高考!");  
    }  
    public static void main(String[] args){
        SenoirStudent stu = new SenoirStudent();
    }
}

當創建一個SenoirStudent實例的時候構造器執行的流程:
這裏寫圖片描述
再來看看堆內存:
首先在方法區加載Object類的信息,然後再在堆區創建一個Object對象
這裏寫圖片描述
接着加載在方法區加載Student類信息,然後再在堆區創建了一個Student對象
這裏寫圖片描述
(此時可以清楚的看到每一個student對象中的實例方法都含有兩個隱式的關鍵詞this和super。this指向對象自己本身,而super執行父類對象。)
最後,在方法區加載SenoirStudent類信息,然後再在堆區創建一個SenoirStudent對象
這裏寫圖片描述


結語

問一問自己是否需要從子類向父類進行向上轉型。如果必須向上轉型,則繼承是必要的,但是如果不需要,則應當好好考慮自己是否需要繼承。
                                     ---《Thinking in java》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章