畢向東Java基礎(chǔ)教程-繼承【上】

概述

  • 多個類中存在相同屬性和行為時,將這些內(nèi)容抽取到單獨一個類中,那么多個類無需再定義這些屬性和行為,只要繼承單獨的類即可。
  • 多個類可以稱為子類,單獨的類稱為父類或者超類。
  • 子類可以訪問父類中的<font color="#FF0000">非私有的</font>屬性和行為。
  • 通過extends關(guān)鍵字讓類與類之間產(chǎn)生繼承關(guān)系。
    class SubDemo extends Demo{}
  • 繼承的好處:
    繼承的出現(xiàn)提高了代碼的復(fù)用性。
    繼承的出現(xiàn)讓類與類之間產(chǎn)生了關(guān)系,提供了多態(tài)的前提。

特點

  • Java中支持單繼承,不支持多繼承
    一個子類只能有一個直接父類,否則若多個父類中有相同成員,會產(chǎn)生調(diào)用的不確定性。

    class SubDemo extends Demo{} //ok
    class SubDemo extends Demo1,Demo2...{} //error
    
  • Java支持多層繼承(繼承體系)

    class A{}
    class B extends A{}
    class C extends B{} 
    

    當要使用一個繼承體系時:
    1)查看該體系中的頂層類,了解該體系的基本功能
    2)創(chuàng)建體系中的最子類對象,完成功能的使用

  • 什么時候定義繼承?
    當類與類之間存在著所屬("is a")關(guān)系的時候,就定義繼承。如xxx是yyy的一種,則xxx extends yyy?;蛘吲袛喔割愔械墓δ苁欠褡宇惗紤?yīng)該具備,若有些不具備,則不存在繼承。不要僅為了獲取其他類中某個功能而去繼承 。

super關(guān)鍵字

this和super的用法很相似。

  • this代表一個本類對象的引用;super代表一個<font color="#FF0000">父類空間</font>(注意并不是代表父類對象,如下代碼,從未定義過父類對象,子類之所以可以獲取父類的內(nèi)容,是因為其持有super引用)。

  • 當本類中的成員和局部變量同名用this區(qū)分;當子父類中的成員變量同名用super區(qū)分父類(如果不使用super,則默認調(diào)用子類自己的成員變量),子類要調(diào)用父類構(gòu)造函數(shù)時,可以使用super關(guān)鍵字。

    Example1

    public class Father 
    {    
        int num = 4;
    }
    class Son extends Father{
        int num = 5;
        void show()
        {
            System.out.println(this.num+"..."+super.num);
        }
    }
    class Demo
    {
        public static void main(String[] args)
        {
            Son s = new Son();
            s.show();
        }
    }
    

    父類比子類先加載。

注意:
問題:父類中的私有內(nèi)容,子類是否具備——實際上子類對于父類中私有的成員變量是繼承了的,即定義子類對象時,在堆中存有父類的私有成員變量(上圖堆中fu的方框內(nèi)),只是不能直接訪問,所以確切地說,應(yīng)該是子類中不能直接地訪問父類中的私有內(nèi)容。

Example2

class Person
{
    private String name;
    private int age;

    Person(String name, int age)
    {
        this.name = name;
        this.age = age;
    }
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
    public int getAge() {return age;}
    public void setAge(int age) {this.age = age;}
}

class Student extends Person
{
    Student(String name, int age)
    {
        super(name, age);//調(diào)用父類的構(gòu)造函數(shù)
    }
}
class Worker extends Person
{
    Worker(String name, int age)
    {
        super(name, age);//調(diào)用父類的構(gòu)造函數(shù)
    }
}

函數(shù)覆蓋(Override)

  • 函數(shù)覆蓋發(fā)生在父類與子類之間,其函數(shù)名、參數(shù)類型、返回值類型必須同父類中的相對應(yīng)被覆蓋的函數(shù)嚴格一致,覆蓋函數(shù)和被覆蓋函數(shù)只有函數(shù)體不同,當派生類對象調(diào)用子類中該同名函數(shù)時會自動調(diào)用子類中的覆蓋版本,而不是父類中的被覆蓋函數(shù)版本,這種機制就叫做函數(shù)覆蓋。
  • 父類中的私有方法不可以被覆蓋。
  • 在子類覆蓋方法中,繼續(xù)使用被覆蓋的方法可以通過super.函數(shù)名獲取。
  • 覆蓋注意事項:
    覆蓋時,子類方法權(quán)限一定要大于等于父類方法權(quán)限。
    靜態(tài)只能覆蓋靜態(tài),或被靜態(tài)覆蓋(非靜態(tài)不能覆蓋靜態(tài),靜態(tài)不能覆蓋非靜態(tài),否則會報編譯錯)。
  • 覆蓋的應(yīng)用:
    當子類需要父類的功能,而功能主體子類有自己特有內(nèi)容時,可以復(fù)寫父類中的方法,這樣即沿襲了父類的功能,又定義了子類特有的內(nèi)容。

子類的實例化過程

注意構(gòu)造函數(shù)不會繼承更不會覆蓋(首先函數(shù)名都不一樣...
子類的實例化過程:子類中所有的構(gòu)造函數(shù)默認都會訪問父類中空參數(shù)的構(gòu)造函數(shù)(因為每一個構(gòu)造函數(shù)的第一行都有一條默認的語句super();之所以會有這樣一條默認語句,是因為子類繼承了父類,獲取到了父類中的內(nèi)容(屬性),所以在使用父類內(nèi)容之前,要先明確父類是如何對這些數(shù)據(jù)初始化的)。

public class Father 
{
    public Father()
    {
        System.out.println("Father run");
    }
}
class Son extends Father
{
    public Son()
    {
        //super(); //默認會調(diào)用父類的空參構(gòu)造函數(shù)
        System.out.println("Son run");
    }
}

當父類中沒有空參數(shù)的構(gòu)造函數(shù)時,子類的構(gòu)造函數(shù)必須通過this或者super語句指定要訪問的構(gòu)造函數(shù)。
注意:super語句必須要定義在子類構(gòu)造函數(shù)的第一行,因為父類的初始化動作要先完成。
如果子類構(gòu)造函數(shù)中使用this調(diào)用了本類構(gòu)造函數(shù),那么就沒有super了,因為super和this都只能定義在第一行。但是可以保證的是,子類中肯定會有其他的構(gòu)造函數(shù)訪問父類的構(gòu)造函數(shù),即子類構(gòu)造函數(shù)中至少有一個訪問父類構(gòu)造函數(shù)。

public class Father 
{
    public Father(int num)
    {
        System.out.println("Father run");
    }
}
class Son extends Father
{
    public Son()
    {
        this(4);
    }

    public Son(int num)
    {
        super(num);
        System.out.println("Son run");
    }

}

注意,類默認的空參構(gòu)造函數(shù)的修飾符與類保持一致。

class Demo
{
    /*
    Demo()
    {
        super(); //父類為Object
        return;
    }
    */
}

Example1:子類的實例化過程

public class Father {
    Father()
    {
        show();
    }
    public  void show()
    {
        System.out.println("father show");
    }
}
class Son extends Father
{
    int num = 8;
    Son()
    {
        super();
        //分水嶺
        System.out.println("Son run..." + num);
    }

    public void show()
    {
        System.out.println("Son show..." + num);
    }
}
class Demo
{
    public static void main(String[] args)
    {
        Son s = new Son();
        s.show();
    }
}

輸出結(jié)果為:

Son show...0
Son run...8
Son show...8

內(nèi)存圖解:

默認初始化,成員變量num=0;顯示初始化,成員變量num=8。
默認初始化后,執(zhí)行super(),經(jīng)過父類構(gòu)造函數(shù)初始化,再進行自己的顯示初始化,最后是自己的構(gòu)造函數(shù)初始化。

注意,若子類當中有構(gòu)造代碼塊,則其初始化的過程為:

一個對象的實例化過程:

Person p = new Person();
1、JVM讀取指定路徑下的Person.class文件,如果該類有直接父類,先加載Person的父類,再加載Person進內(nèi)存;
2、在堆內(nèi)存中開辟空間,分配地址;
3、在堆的對象空間中,對對象中的屬性進行默認初始化;
4、調(diào)用對應(yīng)的構(gòu)造函數(shù)進行初始化;
5、在構(gòu)造函數(shù)中,先調(diào)用父類的構(gòu)造函數(shù)進行初始化;
6、父類初始化完畢后,再對子類的屬性進行顯示初始化;
7、構(gòu)造代碼塊初始化;
8、進行子類構(gòu)造函數(shù)的特定初始化;
9、初始化完畢后,將地址值賦值給引用變量(不一定是在棧中,如下)。

class Demo
{
    Person p = new Person(); //在堆中
}

final關(guān)鍵字

  • final可以修飾類,方法,變量。
  • final修飾的類不可以被繼承。(繼承弊端:打破了封裝性。)
  • final修飾的方法不可以被覆蓋。(可以用于在父類中,若有幾個方法不希望被復(fù)寫時)
  • final修飾的變量是一個常量,只能被賦值一次。(用于在類中值不會變化的,命名方式為所有字母大寫,單詞之間用下劃線連接;為什么要用final修飾變量,其實在程序中若一個數(shù)據(jù)是固定的,那么直接使用這個數(shù)據(jù)就可以了,但是這樣閱讀性差,所以加上final)
  • 內(nèi)部類只能訪問被final修飾的局部變量。

Example1

class Person
{
    final int x;
}
class FinalDemo
{
    public static void main(String[] args){
        new Person();
    }
}

編譯錯誤:可能尚未初始化變量x(指的顯示初始化)。
注意:變量若是有final修飾符,則一般都為靜態(tài):static final int x = 7,每個對象的值都一樣。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容