04-01【繼承、super、this、抽象類】

第一章繼承


1.1概述


由來

多個(gè)類中存在相同屬性和行為時(shí),將這些內(nèi)容抽取到單獨(dú)一個(gè)類中,那么多個(gè)類無需再定義這些屬性和行為,只要繼承那一個(gè)類即可。如圖所示:

其中,多個(gè)類可以稱為子類,單獨(dú)那一個(gè)類稱為父類、超類(superclass)或者基類

繼承描述的是事物之間的所屬關(guān)系,這種關(guān)系是:is-a的關(guān)系。例如,圖中兔子屬于食草動(dòng)物,食草動(dòng)物屬于動(dòng)物??梢?,父類更通用,子類更具體。我們通過繼承,可以使多種事物之間形成一種關(guān)系體系。

定義
  • 繼承:就是子類繼承父類的屬性行為,使得子類對(duì)象具有與父類相同的屬性、相同的行為。子類可以直接 訪問父類中的非私有的屬性和行為。
好處
  1. 提高代碼的復(fù)用性。
  2. 類與類之間產(chǎn)生了關(guān)系,是多態(tài)的前提。

1.2 繼承的格式


通過extends關(guān)鍵字,可以聲明一個(gè)子類繼承另外一個(gè)父類,定義格式如下:

class 父類 {
...
}
class 子類 extends 父類 {
...
}

繼承演示,代碼如下:

/*
*定義員工類Employee,做為父類 
*/ 
class Employee {
    String name; // 定義 name 屬性 
    // 定義員工的工作方法 
    public void work() {
        System.out. println("盡心盡力地工作");     
    }
}
/*
*定義講師類Teacher繼承 員工類Employee
*/
class Teacher extends Employee {
    //定義一個(gè)打印name的方法 
    public void printName() {     
        System.out.println("name=" + name);
    }
}
/*
* 定義測(cè)試類
*/
public class ExtendDemo01 { 
    public static void main(String[] args) {
        // 創(chuàng)建一個(gè)講師類對(duì)象 
        Teacher t = new Teacher();

        //為該員工類的name屬性進(jìn)行賦值     
        t.name = "小明";

        //調(diào)用該員工的printName()方法   
        t.printName(); // name = 小明
        //調(diào)用Teacher類繼承來的work()方法     
        t.work(); // 盡心盡力地工作
    }
}

1.3 繼承后的特點(diǎn)——成員變量


當(dāng)類之間產(chǎn)生了關(guān)系后,其中各類中的成員變量,又產(chǎn)生了哪些影響呢?

成員變量不重名

如果子類父類中出現(xiàn)不重名的成員變量,這時(shí)的訪問是沒有影響的。代碼如下:

class Fu {
    // Fu中的成員變量。 
    int num = 5;
}
class Zi extends Fu {
    // zi中的成員變量
    int num2 = 6;
    // Zi中的成員方法
    public void show() {
        //訪問父類中的num,
        System.out.println("Fu num="+num); // 繼承而來,所以直接訪問。
        //訪問子類中的num2
        System.out.println("Zi num2="+num2);
    }
}
class ExtendDemo02 {
    public static void main(String[] args) {
        // 創(chuàng)建子類對(duì)象
        Zi z = new Zi();
        //調(diào)用子類中的show方法
        z.show();
    }
}
演示結(jié)果:
Fu num = 5
Zi num2 = 6
成員變量重名

如果子類父類中出現(xiàn)重名的成員變量,這時(shí)的訪問是有影響的。代碼如下:

class Fu {
    // Fu中的成員變量。
    int num = 5;
}
class Zi extends Fu {
    // Zi中的成員變量
    int num = 6;
    public void show() {
        //訪問父類中的num
        System.out.println("Fu num=" + num);
        //訪問子類中的num
        System.out.println("Zi num=" + num);
    }
}
class ExtendsDemo03 {
    public static void main(String[] args) {
        // 創(chuàng)建子類對(duì)象
        Zi z = new Zi();
        //調(diào)用子類中的show方法
        z.show();
    }
}
演示結(jié)果:
Fu num = 6
Zi num = 6?

子父類中出現(xiàn)了同名的成員變量時(shí),在子類中需要訪問父類中非私有成員變量時(shí),需要使用super關(guān)鍵字,修飾父類成員變量,類以于之前學(xué)過的this。
使用格式:

super.父類成員變量名

子類方法需要修改,代碼如下:

class Zi extends Fu {
    // Zi中的成員變量
    int num = 6;
    public void show() {
        //訪問父類中的num
        System.out.println("Fu num=" + super.num);
        //訪問子類中的num
        System.out.println("Zi num=" + this.num);
    }
}
演示結(jié)果:
Fu num = 5
Zi num = 6

小貼士: Fu類中的成員變量是非私有的,子類中可以直接訪問。若Fu類中的成員變量私有了,子類是不能直接訪問的。通常編碼時(shí),我們遵循封裝的原則,使用private修飾成員變量,那么如何訪問父類的私有成員 變量呢?對(duì)!可以在父類中提供公共的getXxx方法和setXxx方法。

1.4繼承后的特點(diǎn)一一成員方法


當(dāng)類之間產(chǎn)生了關(guān)系,其中各類中的成員方法,又產(chǎn)生了哪些影響呢?

成員方法不重名

如果子類父類中出現(xiàn)不重名的成員方法,這時(shí)的調(diào)用是沒有影響的。對(duì)象調(diào)用方法時(shí),會(huì)先在子類中查找有沒有對(duì)應(yīng)的方法,若子類中存在就會(huì)執(zhí)行子類中的方法,若子類中不存在就會(huì)執(zhí)行父類中相應(yīng)的方法。代碼如下:

class Fu{
    public void show(){
        System.out. println("Fu類中的show方法執(zhí)行");
    }
}
class Zi extends Fu{
    public void show2(){
        System.out. println("Zi類中的show2方法執(zhí)行");
    }
}
public class ExtendsDemo04{
    public static void main(String[] args) {
        Zi z = new Zi();
        //子類中沒有show方法,但是可以找到父類方法去執(zhí)行?
        z.show(); 
        z.show2();
    }
}
成員方法重名——重寫(Override)

如果子類父類中出現(xiàn)重名的成員方法,這時(shí)的訪問是一種特殊情況,叫做**方法重寫 **(Override)。

  • 方法重寫:子類中出現(xiàn)與父類一模一樣的方法時(shí)(返回值類型,方法名和參數(shù)列表都相同),會(huì)出現(xiàn)覆蓋效果,也稱為重寫或者復(fù)寫。聲明不變,重新實(shí)現(xiàn)。
    代碼如下:
class Fu { 
    public void show() {
        System.out.println("Fu show");
    }
}
class Zi extends Fu {
    //子類重寫了父類的show方法 
    public void show() {
        System.out.println("Zi show");
    }
}
public class ExtendsDemo05{ 
    public static void main(String[] args) {
        Zi z = new Zi();
        //子類中有show方法,只執(zhí)行重寫后的show方法
        z.show(); // Zi show
    }
}
重寫的應(yīng)用

子類可以根據(jù)需要,定義特定于自己的行為。既沿襲了父類的功能名稱,又根據(jù)子類的需要重新實(shí)現(xiàn)父類方法,從 而進(jìn)行擴(kuò)展增強(qiáng)。比如新的手機(jī)增加來電顯示頭像的功能,代碼如下:

class Phone {
    public void sendMessage(){
        System.out.println ("發(fā)短信");
    }
    public void call(){
        System.out.println ("打電話");
    }
    public void showNum(){
        System.out.println("來電顯示號(hào)碼");
    }
} 
//智能手機(jī)類
class NewPhone extends Phone {
    //重寫父類的來電顯示號(hào)碼功能,并增加自己的顯示姓名和圖片功能 
    public void showNum(){
        //調(diào)用父類已經(jīng)存在的功能使用super
        super.showNum();
        //增加自己特有顯示姓名和圖片功能
        System. out. println ("顯示來電姓名");         
        System. out. println ("顯示頭像");
    }
}
public class ExtendsDemo06 {
    public static void main(String[] args) {
        // 創(chuàng)建子類對(duì)象
        NewPhone np = new NewPhone();
        // 調(diào)用父類繼承而來的方法
        np.call();
        // 調(diào)用子類重寫的方法
        np.showNum();
    }
}

小貼士:這里重寫時(shí),用到super?父類成員方法,表示調(diào)用父類的成員方法。

注意事項(xiàng)
  1. 子類方法覆蓋父類方法,必須要保證權(quán)限大于等于父類權(quán)限。
  2. 子類方法覆蓋父類方法,返回值類型、函數(shù)名和參數(shù)列表都要一模一樣。

1.5 繼承后的特點(diǎn)——構(gòu)造方法


當(dāng)類之間產(chǎn)生了關(guān)系,其中各類中的構(gòu)造方法,又產(chǎn)生了哪些影響呢? 首先我們要回憶兩個(gè)事情,構(gòu)造方法的定義格式和作用。

  1. 構(gòu)造方法的名字是與類名一致的。所以子類是無法繼承父類構(gòu)造方法的。
  2. 構(gòu)造方法的作用是初始化成員變量的。所以子類的初始化過程中,必須先執(zhí)行父類的初始化動(dòng)作。子類的構(gòu)造方法中默認(rèn)有一個(gè)super(),表示調(diào)用父類的構(gòu)造方法,父類成員變量初始化后,才可以給子類使用。代碼如下:
class Fu {
    private int n;
    Fu(){ 
        System.out.println("Fu()");
    }
}

class Zi extends Fu {
    Zi(){
        // super (),調(diào)用父類構(gòu)造方法 
        super();
        System.out.println("Zi() ");
    }
}
public class ExtendsDemo07{
    public static void main (String args[]){
        Zi zi = new Zi();
    }
}
輸出結(jié)果:
Fu()
Zi()

1.6 super 和 this


父類空間優(yōu)先于子類對(duì)象產(chǎn)生

在每次創(chuàng)建子類對(duì)象時(shí),先初始化父類空間,再創(chuàng)建其子類對(duì)象本身。目的在于子類對(duì)象中包含了其對(duì)應(yīng)的父類空 間,便可以包含其父類的成員,如果父類成員非private修飾,則子類可以隨意使用父類成員。代碼體現(xiàn)在子類的構(gòu) 造方法調(diào)用時(shí),一定先調(diào)用父類的構(gòu)造方法。理解圖解如下:

super和this的含義
  • super :代表父類的存儲(chǔ)空間標(biāo)識(shí)(可以理解為父親的引用)。
  • this:代表當(dāng)前對(duì)象的引用(誰(shuí)調(diào)用就代表誰(shuí))。
super和this的用法
  1. 訪問成員

this.成員變量 -- 本類的
super.成員變量 -- 父類的
this.成員方法名() -- 本類的
super.成員方法名()-- 父類的

用法演示,代碼如下:

class Animal { 
    public void eat() {
        System.out.println("animal : eat"); }
    }
}
class Cat extends Animal { 
    public void eat() {
        System.out.println("cat : eat"); 
    }
    public void eatTest() {
        this.eat(); // this 調(diào)用本類的方法     
        super.eat(); // super 調(diào)用父類的方法
    }
}
class ExtendsDemo08 { 
    public static void main(String[] args) {
        Animal a = new Animal(); 
        a.eat();
        Cat c = new Cat(); 
        c.eatTest();
    }
}
輸出結(jié)果為:
animal : eat
cat : eat
animal : eat
  1. 訪問構(gòu)造方法
this(...)   --  本類的構(gòu)造方法
super(...)  --  父類的構(gòu)造方法

子類的每個(gè)構(gòu)造方法中均有默認(rèn)的super(),調(diào)用父類的空參構(gòu)造。手動(dòng)調(diào)用父類構(gòu)造會(huì)覆蓋默認(rèn)的super()。

super() 和 this() 都必須是在構(gòu)造方法的第一行,所以不能同時(shí)出現(xiàn)。

1.7 繼承的特點(diǎn)


  1. Java只支持單繼承,不支持多繼承。
//一個(gè)類只能有一個(gè)父類,不可以有多個(gè)父類。
class C extends A{} //ok 
class C extends A,B... //error
  1. Java支持多層繼承(繼承體系)。
class A{}
class B extends A{}
class C extends B{}

頂層父類是Object類。所有的類默認(rèn)繼承Object,作為父類。

  1. 子類和父類是一種相對(duì)的概念。

第二章 抽象類


2.1概述


由來

父類中的方法,被它的子類們重寫,子類各自的實(shí)現(xiàn)都不盡相同。那么父類的方法聲明和方法主體,只有聲明還有 意義,而方法主體則沒有存在的意義了。我們把沒有方法主體的方法稱為抽象方法。Java語(yǔ)法規(guī)定,包含抽象方法的類就是抽象類。

定義
  • 抽象方法:沒有方法體的方法。
  • 抽象類:包含抽象方法的類。

2.2 abstract使用格式


抽象方法

使用abstract關(guān)鍵字修飾方法,該方法就成了抽象方法,抽象方法只包含一個(gè)方法名,而沒有方法體。
定義格式:

修飾符 abstract 返回值類型 方法名(參數(shù)列表);

代碼舉例:

public abstract void run() ;
抽象類

如果一個(gè)類包含抽象方法,那么該類必須是抽象類。
定義格式:

abstract class 類名字 {

}

代碼舉例:

public abstract class Animal { 
    public abstract void run();
}
抽象的使用

繼承抽象類的子類必須重寫父類所有的抽象方法。否則,該子類也必須聲明為抽象類。最終,必須有子類實(shí)現(xiàn)該父類的抽象方法,否則,從最初的父類到最終的子類都不能創(chuàng)建對(duì)象,失去意義。
代碼舉例:

public class Cat extends Animal {
    public void run (){
        System.out.println(" 小貓?jiān)趬︻^走 ~~~") ; 
    }
}
public class CatTest {
    public static void main(String[] args) { 
        // 創(chuàng)建子類對(duì)象 
        Cat c = new Cat();

        // 調(diào)用 run 方法
        c.run();
    }
}
輸出結(jié)果: 
小貓?jiān)趬︻^走~~~

此時(shí)的方法重寫,是子類對(duì)父類抽象方法的完成實(shí)現(xiàn),我們將這種方法重寫的操作,也叫做實(shí)現(xiàn)方法

2.3 注意事項(xiàng)


關(guān)于抽象類的使用,以下為語(yǔ)法上要注意的細(xì)節(jié),雖然條目較多,但若理解了抽象的本質(zhì),無需死記硬背。

  1. 抽象類不能創(chuàng)建對(duì)象,如果創(chuàng)建,編譯無法通過而報(bào)錯(cuò)。只能創(chuàng)建其非抽象子類的對(duì)象。

理解:假設(shè)創(chuàng)建了抽象類的對(duì)象,調(diào)用抽象的方法,而抽象方法沒有具體的方法體,沒有意義。

  1. 抽象類中,可以有構(gòu)造方法,是供子類創(chuàng)建對(duì)象時(shí),初始化父類成員使用的。

理解:子類的構(gòu)造方法中,有默認(rèn)的super(),需要訪問父類構(gòu)造方法。

  1. 抽象類中,不一定包含抽象方法,但是有抽象方法的類必定是抽象類。

理解:未包含抽象方法的抽象類,目的就是不想讓調(diào)用者創(chuàng)建該類對(duì)象,通常用于某些特殊的類結(jié)構(gòu)設(shè)計(jì)。

  1. 抽象類的子類,必須重寫抽象父類中所有的抽象方法,否則,編譯無法通過而報(bào)錯(cuò)。除非該子類也是抽象類。

理解:假設(shè)不重寫所有抽象方法,則類中可能包含抽象方法。那么創(chuàng)建對(duì)象后,調(diào)用抽象的方法,沒有意義。

第三章 繼承的綜合案例


3.1 綜合案例:群主發(fā)普通紅包


群主發(fā)普通紅包。某群有多名成員,群主給成員發(fā)普通紅包。普通紅包的規(guī)則:

  1. 群主的一筆金額,從群主余額中扣除,平均分成n等份,讓成員領(lǐng)取。
  2. 成員領(lǐng)取紅包后,保存到成員余額中。

請(qǐng)根據(jù)描述,完成案例中所有類的定義以及指定類之間的繼承關(guān)系,并完成發(fā)紅包的操作。

3.2 案例分析


根據(jù)描述分析,得出如下繼承體系:

3.3 案例實(shí)現(xiàn)

定義用戶類:

public class User {
    // 成員變量
    private String username;    // 用戶名
    private double leftMoney;   // 余額
    // 構(gòu)造方法 
    public User() { }
    public User(String username, double leftMoney) { 
        this.username = username;
        this.leftMoney = leftMoney;
    }
    // get/se t 方法 
    public String getUsername() { 
        return username;
    }
    public void setUsername(String username) { 
        this.username = username;
    }
    public double getLeftMoney() {
        return leftMoney;
    }
    public void setLeftMoney(double leftMoney) { 
        this.leftMoney = leftMoney;
    }
    // 展示信息的方法
    public void show() {
        System.out.println(" 用戶名 :"+ username +" , 余額為 :" + leftMoney + " 元 "); 
    }
}

定義群主類:

public class QunZhu extends User {
   // 添加構(gòu)造方法
   public QunZhu() {
   }
   public QunZhu(String username, double leftMoney) {
       //通過super調(diào)用父類構(gòu)造方法     
       super(username, leftMoney);
   }
   /* 
       群主發(fā)紅包,就是把一個(gè)整數(shù)的金額,分層若干等份。
       1. 獲取群主余額,是否夠發(fā)紅包.
           不能則返回null,并提示.
           能則繼續(xù).
       2. 修改群主余額.
       3. 拆分紅包.
           3.1 如果能整除,那么就平均分。
           3.2 如果不能整除,那么就把余數(shù)分給最后一份。
   */
   public ArrayList<Double> send(int money, int count) {
       // 獲取群主余額
       double leftMoney = getLeftMoney();
       if(money > leftMoney) {?
           return null;
       }
       // 修改群主余額的
       setLeftMoney(leftMoney - money);
       // 創(chuàng)建一個(gè)集合 , 保存等份金額
       ArrayList<Double> list = new ArrayList<>();
       // 擴(kuò)大100倍,相當(dāng)于折算成'分' 為單位,避免小數(shù)運(yùn)算損失精度的問題 
       money = money * 100;
       // 每份的金額
       int m = money / count;
       // 不能整除的余數(shù)
       int l = money % count;
       //無論是否整除,n-1份,都是每份的等額金額
       for (int i = 0; i < count - 1; i++) {
           // 縮小100倍,折算成 '元'
           list.add(m / 100.0);
       }
       // 判斷是否整除
       if (l == 0) {
           // 能整除, 最后一份金額,與之前每份金額一致 
           list.add(m / 100.0);
       } else {
           // 不能整除, 最后一份的金額,是之前每份金額+余數(shù)金額 
           list.add((m + 1) / 100.00);
       }
       // 返回集合
       return list;
   }
}

定義成員類:

public class Member extends User {
    public Member() {
    }
    public Member(String username, double leftMoney) { 
        super(username, leftMoney);
    }
    // 打開紅包,就是從集合中,隨機(jī)取出一份,保存到自己的余額中 
    public void openHongbao(ArrayList<Double> list) {
        //創(chuàng)建Random對(duì)象
        Random r = new Random();
        // 隨機(jī)生成一個(gè)角標(biāo)
        int index = r.nextInt(list.size());
        // 移除一個(gè)金額?
        Double money = list.remove(index); // 直接調(diào)用父類方法,設(shè)置到余額         
        setLeftMoney( money );
    }
}

定義測(cè)試類:

public class Test {
    public static void main(String[] args) {

        // 創(chuàng)建一個(gè)群主對(duì)象
        QunZhu qz = new QunZhu("群主",200);

        // 創(chuàng)建一個(gè)鍵盤錄入
        Scanner sc = new Scanner();
        System.out.println ("請(qǐng)輸入金額:");
        int money = sc.nextInt();
        System.out.println("請(qǐng)輸入個(gè)數(shù):");
        int count = sc.nextInt();

        // 發(fā)送紅包
        ArrayList<Double> sendList = s.send(money,count);

        // 判斷 , 如果余額不足
        if(sendList == null){
            System.out.println(" 余額不足...");     
            return;
        }
        // 創(chuàng)建三個(gè)成員
        Member m = new Member();
        Member m2 = new Member();
        Member m3 = new Member();
        // 打開紅包
        m.openHongbao(sendList);
        m2.openHongbao(sendList);
        m3.openHongbao(sendList);
        // 展示信息
        qz.show();
        m.show();
        m2.show();
        m3.show();
    }
}

課后請(qǐng)同學(xué)自己思考并完成擴(kuò)展需求。
案例擴(kuò)展:

  1. 如果成員的余額不為0呢,將如何處理?
  2. 如果群主想輸入帶小數(shù)的金額呢,將如何處理?
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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