Java面向對象編程三大特徵 - 繼承

寫在前面:博主是一隻經過實戰開發歷練後投身培訓事業的“小山豬”,暱稱取自動畫片《獅子王》中的“彭彭”,總是以樂觀、積極的心態對待周邊的事物。本人的技術路線從Java全棧工程師一路奔向大數據開發、數據挖掘領域,如今終有小成,願將昔日所獲與大家交流一二,希望對學習路上的你有所助益。同時,博主也想通過此次嘗試打造一個完善的技術圖書館,任何與文章技術點有關的異常、錯誤、注意事項均會在末尾列出,歡迎大家通過各種方式提供素材。

  • 對於文章中出現的任何錯誤請大家批評指出,一定及時修改。
  • 有任何想要討論和學習的問題可聯繫我:[email protected]
  • 發佈文章的風格因專欄而異,均自成體系,不足之處請大家指正。

Java面向對象編程三大特徵 - 繼承

本文關鍵字:Java、面向對象、三大特徵、繼承


繼承是面向對象編程的三大特徵之一,繼承將面向對象的編程思想體現的更加淋漓盡致,允許類和類之間產生關聯,對於類和對象的基本知識可進傳送門:Java中的基本操作單元 - 類和對象

一、思想解讀

1. 什麼是繼承

從類的概念出發,我們可以通過定義class去描述一類事物,具有相同的屬性和行爲。但在很多時候我們希望對類的定義能夠進一步細化,這就相當於是一個大的分類下面有很多的子分類,如文具下面可以分爲:寫字筆、便籤、文件管理等等。

如果品類更加的複雜,可以先分爲:學生文具、辦公文具、財會用品,然後在每個品類下面再根據具體的作用去劃分。這些被劃分出來的子類別都一定具有父類別的某些共同特徵或用途,並且有可能存在多級的分類關係,那麼如果我們使用面向對象的語言去描述出這樣一種關係就可以使用繼承。

下面我們將例子與面向對象中的概念進行對應:

  • 上述關係可以用子類別繼承自父類別來描述
  • 父類別被稱作父類或超類
  • 子類別被稱作子類
  • 繼承可以使子類具有父類的各種屬性和方法,不需要再次編寫相同的代碼

2. 繼承有什麼用

如果我們將學生類進一步細化爲:初中生、高中生、大學生。顯然,細化之後的類與類之間一定是存在某些差異的,但是也一定會存在共同點。如果我們使用代碼進行表示,三個類中會有很多相同的屬性或方法,也會存在一些差異:

// 定義類:初中生
public class JuniorStudent{
    // 相同的屬性
    public String name;
    public int age;
    public String school;
    public String grade;
    // 其他方法
}
// 定義類:高中生
public class SeniorStudent{
    // 相同的屬性
    public String name;
    public int age;
    public String school;    
    public String grade;
    // 不同的屬性
    public String subject;// 科目:文理科
    // 其他方法
}
// 定義類:大學生
public class UniversityStudent{
    // 相同的屬性
    public String name;
    public int age;
    public String school;
    public String grade;
    // 不同的屬性
    public String college;// 學院
    public String major;// 專業
    // 其他方法
}

上面只是列舉了部分的屬性,可以發現有很多屬性是完全重合的,方法也有可能存在相同的現象。這個時候我們就們就可以將其中相同的屬性和方法抽取出來,定義一個Student學生類,從而對每個類進行簡化。

// 定義類:學生
public class Student{
    // 提取公共的屬性
    public String name;
    public int age;
    public String school;
    public String grade;
    // 提取公共的方法
}
// 簡化後的初中生
public class JuniorStudent extends Student{
    // 其他方法
}
// 簡化後的高中生
public class SeniorStudent extends Student{
    // 不同的屬性
    public String subject;// 科目:文理科
    // 其他方法
}
// 簡化後的大學生
public class UniversityStudent extends Student{
    // 不同的屬性
    public String college;// 學院
    public String major;// 專業
    // 其他方法
}
// 定義測試類
public class Test{
    public static void main(String[] args){
        JuniorStudent juniorStudent = new JuniorStudent();
        juniorStudent.name = "小明";// 正常使用,來自父類
        SeniorStudent seniorStudent = new SeniorStudent();
        seniorStudent.name = "小李";// 正常使用,來自父類
        seniorStudent.subject = "文科";// 自有屬性,來自子類
        UniversityStudent universityStudent = new UniversityStudent();
        universityStudent.name = "小陳";// 正常使用,來自父類
        universityStudent.college = "XX大學";// 自有屬性,來自子類
        universityStudent.major = "XX專業";// 自有屬性,來自子類
    }
}

從上面的例子可以看出,子父類之間可以通過extends關鍵字建立繼承關係。子類可以直接使用父類中定義的屬性和方法,也可以覆蓋父類中的方法,表現出子類自己的特點。使用繼承有以下幾個好處:

  • 減少代碼量,子類可以繼承父類的屬性和方法
  • 提高複用性,便於維護
  • 子類可以通過覆蓋父類的方法表達自身的特點
  • 可以使類和類之間產生關聯,是多態的前提

3. 繼承的限制與規則

在Java中,繼承的使用存在一些限制,我們需要先明確使用規則才能更好的去設計子父類。一言以蔽之:Java不支持多繼承,但支持多重繼承(多級繼承),從一個子類出發,只存在一條路找到最終的父類

  • 單繼承
class A{
    ...
}
class B extends A{
    ...
}
  • 多重繼承
class A{
    ...
}
class B extends A{
    ...
}
class C extends B{
    ...
}
  • 多子類繼承同一父類
class A{
    ...
}
class B extends A{
    ...
}
class C extends A{
    ...
}

4. 如何設計子父類

當我們需要通過程序去描述某一個場景或實現某一個應用系統時,就需要構建很多相關的類,合理的使用繼承可以使代碼更加高效也更加利於維護。那麼子父類的構建就可以從類本身所代表的意義出發,如果含義相似或相近,並且類與類之間沒有較大的衝突,那麼我們就可以把他們歸爲一類。另外一種情況就是原有構建的類不能滿足新功能的需要,需要據此改進,那麼我們就可以將原有類作爲父類,擴充出他的子類,使整體的功能更加強大,同時又不會對已有的代碼產生較大的影響。

  • 從多個相關聯的類中提取出父類

可以從管理員(AdminUser)、普通用戶(NormalUser)、VIP用戶(VIPUser)中提取相同的特徵,得到父類:用戶(User),同樣具有用戶名,密碼,暱稱等信息,同樣存在登錄方法,只不過各自的實現會有所不同。我們也可以混合使用多種繼承方式,得到如下的類關係:

  • 從一個已有的類中擴充出子類

對於一個簡單的電商場景,產品類的設計會比較簡單,只需要標識基本信息和價格即可。如果此時需要舉行一個秒殺活動,要在購買頁面中標識出原價、特價、活動時間、活動介紹等等信息,這就使得我們要對產品類做出升級,如果直接去修改產品類,會導致出現一些可能不會經常使用的屬性和方法,因爲這些屬性和方法純粹是爲特價商品而設計的。比較好的做法是將原有的商品類(Product)作爲父類,然後擴充出它的子類:特價商品類(SpecialProduct),在特價商品類中存放新出現的信息。

  • 子類是父類的一個擴充和擴展,使用extends關鍵字來表示真的是很恰當

二、子父類的使用

理解了相關的概念後,我們回到Java的語法中來,子父類間通過extends來建立繼承關係,結構如下:

// 定義父類
public class Father{
    ...
}
// 定義子類,繼承父類
public class Son extends Father{
    ...
}

1. 權限修飾符

在之前的文章中,已經介紹了權限修飾符的用法,不清楚的同學可以進傳送門:Java面向對象編程三大特徵 - 封裝。當兩個類建立了繼承關係時,雖然父類當中的所有內容均會被子類繼承,但是由於存在權限修飾符,無訪問權限的屬性或方法會被隱藏,無法被調用和訪問(實例化子類對象時,父類對象也會一同被實例化,詳細過程會在後面的文章中單獨說明)。
在子類中可以直接調用父類中被public和protected聲明的屬性和方法,如果是在測試類中,在進行屬性調用時依然會受到權限修飾符的限制,看下面一個例子:

src
└──edu
    └──sandtower
        └──bean
            │    Father.java
            │    Son.java
        └──test
            │    Test.java

以上爲實體類與測試類所在的目錄結構

  • Father實體類所在包:edu.sandtower.bean
  • Son實體類爲Father的子類,與Father在同一包下
  • Test測試類所在包:edu.sandtower.test
package edu.sandtower.bean;

public class Father{
    // 父類中的私有屬性
    private double ownMoney = 2000;// 私房錢
    // 父類中的受保護屬性
    protected double money = 5000;
    // 父類中的公開屬性
    public String name = "老李";
}
package edu.sandtower.bean;

public class Son extends Father{
    // 子類中的獨有屬性
    ...
    // 測試方法
    public void test(){
        Son son = new Son();
        System.out.println(son.ownMoney);// 編譯失敗,無法訪問私有屬性,查看私房錢
        System.out.println(son.money);// 編譯通過,在子類中可以訪問protected屬性
        System.out.println(son.name);// 編譯通過,可以訪問public屬性
    }
}
package edu.sandtower.test;

import edu.sandtower.bean.Son;

public class Test{
    public static void main(String[] args){
        // 在test包中的Test類中創建Son實例
        Son son = new Son();
        son.name = "小李";// 編譯通過,可以訪問自父類繼承的公開屬性
        // 編譯失敗,在測試類中無法訪問protected屬性,因爲Test類與Father類並無子父類關係
        son.money -= 500.0;
        // 對於Son的其他屬性和Father的使用可以自行進行測試,不再贅述
    }
}

從上面的例子可以看到,權限修飾符所起的作用還是很大的。測試類對於子父類來說是一個處在不同包中的完全無關的類,在調用時會被權限修飾符所限制,所以這裏也再度明確一下:權限修飾符是根據類的所在路徑與類之間的結構關係進行限定的,不是說在任意一個地方使用子類實例都能調用出父類中的屬性和方法。

2. this與super

明確了權限修飾符的作用規則後就帶來了一個問題,既然在其他類中不能夠訪問某些屬性,那應該如何賦值和使用呢?這時就可以使用封裝的辦法,同時結合this和super的使用來解決。

  • this:指代當前對象,可以調用當前類中的屬性和方法
  • super:指代父類對象,可以調用父類中可訪問的屬性和方法,包括被子類覆蓋重寫的方法

在使用子類實例時,如果我們想要使用某些父類的屬性或方法,可以藉助構造器和封裝方法。將代碼修改後,得到如下結果:

package edu.sandtower.bean;

public class Father{
    // 父類中的私有屬性
    private double ownMoney = 2000;// 私房錢
    // 父類中的受保護屬性
    protected double money = 5000;
    // 父類中的公開屬性
    public String name = "老李";
}
package edu.sandtower.bean;

public class Son extends Father{
    // 子類中的獨有屬性
    ...
    // 使用構造器爲屬性賦值
    public Son(String name,double money){
        super.name = name;
        super.money = money;
    }
    // 使用封裝方法操作父類中的屬性
    public void setMoney(double money){
        super.money = money;
    }
    public double getMoney(){
        return super.money;
    }
}
package edu.sandtower.test;

import edu.sandtower.bean.Son;

public class Test{
    public static void main(String[] args){
        // 在test包中的Test類中創建Son實例
        Son son = new Son("小李",3000);// 成功爲父類繼承而來的屬性賦值
        // 以下代碼編譯通過
        double money = son.getMoney();
        System.out.println(money);
        son.setMoney(money - 500);
    }
}

3. final修飾符

final修飾符可以用來修飾屬性和方法,也可以用來修飾類。
當修飾屬性時,如果是基本數據類型,則可看做是定義了一個常量,值一旦被指定則不可變。如果是引用類型,則引用無法發生變化,即:可以修改數組或實例中的屬性值,但是引用的指向不能再發生變化,無法再指向其他的實例和數組。

由final修飾的方法被子類繼承後不能被重寫,有關於繼承中子父類方法的關係將在論述多態的文章中詳細討論
由final修飾的class不能被繼承,如果我們把繼承關係想象成一棵大樹,父類爲根,子類爲枝的話,那麼final class就一定只能做樹葉了,因爲確認不會有它的子類存在了。

4. 終極父類:Object

在剛接觸面向對象時,我們可能就聽說過一個類,那就是:Object,好像它無所不能裝,大餅夾一切的存在。我們所有定義的class都會隱式的繼承Object,即:如果我們的類沒有使用extends關鍵字顯示的指定父類,那麼會自動認爲Object是父類,這一過程是在JVM運行時完成的,所以我們不能通過反編譯來進行驗證。
Object中提供了特別通用的方法,如:toString,hashCode,equals等等。那麼爲什麼使用Object能裝下一切呢?首先就是因爲Object類一定是最終父類的存在,換句話說就是樹根本根!因爲如果一個類顯示的指定了另外一個類作爲父類,那麼他的父類或者父類的父類,一定會在某一級隱式的繼承Object。
如果想進一步瞭解爲什麼任意類型的對象實例都能使用Object類型的引用接收可以查看另外一篇文章:Java面向對象編程三大特徵 - 多態

Java小白修煉手冊

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章