Java的繼承

瑪拉_以琳發表於2023-02-27

1 概述

1.1 引入

假如我們要定義如下類:
學生類,老師類和工人類,分析如下。

  1. 學生類
    屬性:姓名,年齡
    行為:吃飯,睡覺
  2. 老師類
    屬性:姓名,年齡,薪水
    行為:吃飯,睡覺,教書
  3. 班主任
    屬性:姓名,年齡,薪水
    行為:吃飯,睡覺,管理

如果我們定義了這三個類去開發一個系統,那麼這三個類中就存在大量重複的資訊(屬性:姓名,年齡。行為:吃飯,睡覺)。這樣就導致了相同程式碼大量重複,程式碼顯得很臃腫和冗餘,那麼如何解決呢?

假如多個類中存在相同屬性和行為時,我們可以將這些內容抽取到單獨一個類中,那麼多個類無需再定義這些屬性和行為,只要繼承那一個類即可。如圖所示:
1.jpg

其中,多個類可以稱為子類,單獨被繼承的那一個類稱為父類超類(superclass)或者基類

1.2 繼承的含義

繼承描述的是事物之間的所屬關係,這種關係是:is-a 的關係。例如,兔子屬於食草動物,食草動物屬於動物。可見,父類更通用,子類更具體。我們透過繼承,可以使多種事物之間形成一種關係體系。

繼承:就是子類繼承父類的屬性行為,使得子類物件可以直接具有與父類相同的屬性、相同的行為。子類可以直接訪問父類中的非私有的屬性和行為。

1.3 繼承的好處

  1. 提高程式碼的複用性(減少程式碼冗餘,相同程式碼重複利用)。
  2. 使類與類之間產生了關係。

2 繼承的格式

透過 extends 關鍵字,可以宣告一個子類繼承另外一個父類,定義格式如下:

class 父類 {
    ...
}

class 子類 extends 父類 {
    ...
}

需要注意:Java是單繼承的,一個類只能繼承一個直接父類,跟現實世界很像,但是Java中的子類是更加強大的。

3 繼承案例

3.1 案例

請使用繼承定義以下類:

  1. 學生類
    屬性:姓名,年齡
    行為:吃飯,睡覺
  2. 老師類
    屬性:姓名,年齡,薪水
    行為:吃飯,睡覺,教書
  3. 班主任
    屬性:姓名,年齡,薪水
    行為:吃飯,睡覺,管理

3.2 案例圖解分析

老師類,學生類,還有班主任類,實際上都是屬於人類的,我們可以定義一個人類,把他們相同的屬性和行為都定義在人類中,然後繼承人類即可,子類特有的屬性和行為就定義在子類中了。

如下圖所示。

360截圖20181202210617427.jpg

3.3 案例程式碼實現

1.父類Human類

 public class Human {
  // 合理隱藏
  private String name ;
  private int 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;
  }
 }

2.子類Teacher類

public class Teacher extends Human {
  // 工資
  private double salary ;
  
  // 特有方法
  public void teach(){
      System.out.println("老師在認真教技術!");
  }

  public double getSalary() {
      return salary;
  }

  public void setSalary(double salary) {
      this.salary = salary;
  }
}

3.子類Student類

public class Student extends Human{
 
}

4.子類BanZhuren類

public class Teacher extends Human {
    // 工資
    private double salary ;
    
       // 特有方法
    public void admin(){
        System.out.println("班主任強調紀律問題!");
    }
    
    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}

5.測試類

public class Test {
    public static void main(String[] args) {
        Teacher dlei = new Teacher();
        dlei.setName("播仔");
        dlei.setAge("31");
        dlei.setSalary(1000.99);
        System.out.println(dlei.getName());
        System.out.println(dlei.getAge());
        System.out.println(dlei.getSalary());
        dlei.teach();
        
        BanZhuRen linTao = new BanZhuRen();
        linTao.setName("靈濤");
        linTao.setAge("28");
        linTao.setSalary(1000.99);
        System.out.println(linTao.getName());
        System.out.println(linTao.getAge());
        System.out.println(linTao.getSalary());
        linTao.admin();

        Student xugan = new Student();
        xugan.setName("播仔");
        xugan.setAge("31");
        //xugan.setSalary(1000.99); // xugan沒有薪水屬性,報錯!
        System.out.println(xugan.getName());
        System.out.println(xugan.getAge());



    }
}

3.4 小結

1.繼承實際上是子類相同的屬性和行為可以定義在父類中,子類特有的屬性和行為由自己定義,這樣就實現了相同屬性和行為的重複利用,從而提高了程式碼複用。

2.子類繼承父類,就可以直接得到父類的成員變數和方法。是否可以繼承所有成分呢?請看下節!

4 子類不能繼承的內容

4.1 引入

並不是父類的所有內容都可以給子類繼承的:

子類不能繼承父類的構造方法。

值得注意的是子類可以繼承父類的私有成員(成員變數,方法),只是子類無法直接訪問而已,可以透過getter/setter方法訪問父類的private成員變數。

4.1 演示程式碼

public class Demo03 {
    public static void main(String[] args) {
        Zi z = new Zi();
        System.out.println(z.num1);
//        System.out.println(z.num2); // 私有的子類無法使用
        // 透過getter/setter方法訪問父類的private成員變數
        System.out.println(z.getNum2());

        z.show1();
        // z.show2(); // 私有的子類無法使用
    }
}

class Fu {
    public int num1 = 10;
    private int num2 = 20;

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

    private void show2() {
        System.out.println("show2");
    }

    public int getNum2() {
        return num2;
    }

    public void setNum2(int num2) {
        this.num2 = num2;
    }
}

class Zi extends Fu {
}

5 繼承後的特點—成員變數

當類之間產生了繼承關係後,其中各類中的成員變數,又產生了哪些影響呢?

5.1 成員變數不重名

如果子類父類中出現不重名的成員變數,這時的訪問是沒有影響的。程式碼如下:

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 Demo04 {
    public static void main(String[] args) {
        // 建立子類物件
        Zi z = new Zi(); 
          // 呼叫子類中的show方法
        z.show();  
    }
}

演示結果:
Fu num = 5
Zi num2 = 6

5.2 成員變數重名

如果子類父類中出現重名的成員變數,這時的訪問是有影響的。程式碼如下:

class Fu1 {
    // Fu中的成員變數。
    int num = 5;
}
class Zi1 extends Fu1 {
    // Zi中的成員變數
    int num = 6;
  
    public void show() {
        // 訪問父類中的num
        System.out.println("Fu num=" + num);
        // 訪問子類中的num
        System.out.println("Zi num=" + num);
    }
}
class Demo04 {
    public static void main(String[] args) {
          // 建立子類物件
        Zi1 z = new Zi1(); 
          // 呼叫子類中的show方法
        z1.show(); 
    }
}
演示結果:
Fu num = 6
Zi num = 6

子父類中出現了同名的成員變數時,子類會優先訪問自己物件中的成員變數。如果此時想訪問父類成員變數如何解決呢?我們可以使用super關鍵字。

5.3 super訪問父類成員變數

子父類中出現了同名的成員變數時,在子類中需要訪問父類中非私有成員變數時,需要使用super 關鍵字,修飾父類成員變數,類似於之前學過的 this

需要注意的是:super代表的是父類物件的引用,this代表的是當前物件的引用。

使用格式:

super.父類成員變數名

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

class Fu {
    // Fu中的成員變數。
    int num = 5;
}

class Zi extends Fu {
    // Zi中的成員變數
    int num = 6;
  
    public void show() {
        int num = 1;
      
        // 訪問方法中的num
        System.out.println("method num=" + num);
        // 訪問子類中的num
        System.out.println("Zi num=" + this.num);
        // 訪問父類中的num
        System.out.println("Fu num=" + super.num);
    }
}

class Demo04 {
    public static void main(String[] args) {
          // 建立子類物件
        Zi1 z = new Zi1(); 
          // 呼叫子類中的show方法
        z1.show(); 
    }
}

演示結果:
method num=1
Zi num=6
Fu num=5
小貼士:Fu 類中的成員變數是非私有的,子類中可以直接訪問。若Fu 類中的成員變數私有了,子類是不能直接訪問的。通常編碼時,我們遵循封裝的原則,使用private修飾成員變數,那麼如何訪問父類的私有成員變數呢?對!可以在父類中提供公共的getXxx方法和setXxx方法。

6 繼承後的特點—成員方法

當類之間產生了關係,其中各類中的成員方法,又產生了哪些影響呢?

6.1 成員方法不重名

如果子類父類中出現不重名的成員方法,這時的呼叫是沒有影響的。物件呼叫方法時,會先在子類中查詢有沒有對應的方法,若子類中存在就會執行子類中的方法,若子類中不存在就會執行父類中相應的方法。程式碼如下:

class Fu {
    public void show() {
        System.out.println("Fu類中的show方法執行");
    }
}
class Zi extends Fu {
    public void show2() {
        System.out.println("Zi類中的show2方法執行");
    }
}
public  class Demo05 {
    public static void main(String[] args) {
        Zi z = new Zi();
         //子類中沒有show方法,但是可以找到父類方法去執行
        z.show(); 
        z.show2();
    }
}

6.2 成員方法重名

如果子類父類中出現重名的成員方法,則建立子類物件呼叫該方法的時候,子類物件會優先呼叫自己的方法。

程式碼如下:

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方法,只執行重寫後的show方法
        z.show();  // Zi show
    }
}

7 方法重寫

7.1 概念

方法重寫 :子類中出現與父類一模一樣的方法時(返回值型別,方法名和引數列表都相同),會出現覆蓋效果,也稱為重寫或者複寫。宣告不變,重新實現

7.2 使用場景與案例

發生在子父類之間的關係。
子類繼承了父類的方法,但是子類覺得父類的這方法不足以滿足自己的需求,子類重新寫了一個與父類同名的方法,以便覆蓋父類的該方 法。

例如:我們定義了一個動物類程式碼如下:

public class Animal  {
    public void run(){
        System.out.println("動物跑的很快!");
    }
    public void cry(){
        System.out.println("動物都可以叫~~~");
    }
}

然後定義一個貓類,貓可能認為父類cry()方法不能滿足自己的需求

程式碼如下:

public class Cat extends Animal {
    public void cry(){
        System.out.println("我們一起學貓叫,喵喵喵!喵的非常好聽!");
    }
}

public class Test {
    public static void main(String[] args) {
          // 建立子類物件
          Cat ddm = new Cat();
        // 呼叫父類繼承而來的方法
        ddm.run();
          // 呼叫子類重寫的方法
          ddm.cry();
    }
}

7.2 @Override重寫註解

  • @Override:註解,重寫註解校驗!
  • 這個註解標記的方法,就說明這個方法必須是重寫父類的方法,否則編譯階段報錯。
  • 建議重寫都加上這個註解,一方面可以提高程式碼的可讀性,一方面可以防止重寫出錯!

    加上後的子類程式碼形式如下:

    public class Cat extends Animal {
         // 宣告不變,重新實現
        // 方法名稱與父類全部一樣,只是方法體中的功能重寫寫了!
        @Override
        public void cry(){
            System.out.println("我們一起學貓叫,喵喵喵!喵的非常好聽!");
        }
    }

7.3 注意事項

  1. 方法重寫是發生在子父類之間的關係。
  2. 子類方法覆蓋父類方法,必須要保證許可權大於等於父類許可權。
  3. 子類方法覆蓋父類方法,返回值型別、函式名和引數列表都要一模一樣。

8 繼承後的特點—構造方法

8.1 引入

當類之間產生了關係,其中各類中的構造方法,又產生了哪些影響呢?
首先我們要回憶兩個事情,構造方法的定義格式和作用。

  1. 構造方法的名字是與類名一致的。所以子類是無法繼承父類構造方法的。
  2. 構造方法的作用是初始化物件成員變數資料的。所以子類的初始化過程中,必須先執行父類的初始化動作。子類的構造方法中預設有一個super() ,表示呼叫父類的構造方法,父類成員變數初始化後,才可以給子類使用。(先有爸爸,才能有兒子

繼承後子類構方法器特點:子類所有構造方法的第一行都會預設先呼叫父類的無參構造方法

8.2 案例演示

按如下需求定義類:

  1. 人類
    成員變數: 姓名,年齡
    成員方法: 吃飯
  2. 學生類
    成員變數: 姓名,年齡,成績
    成員方法: 吃飯

程式碼如下:

class Person {
    private String name;
    private int age;

    public Person() {
        System.out.println("父類無參");
    }

    // getter/setter省略
}

class Student extends Person {
    private double score;

    public Student() {
        //super(); // 呼叫父類無參,預設就存在,可以不寫,必須再第一行
        System.out.println("子類無參");
    }
    
     public Student(double score) {
        //super();  // 呼叫父類無參,預設就存在,可以不寫,必須再第一行
        this.score = score;    
        System.out.println("子類有參");
     }

}

public class Demo07 {
    public static void main(String[] args) {
        Student s1 = new Student();
        System.out.println("----------");
        Student s2 = new Student(99.9);
    }
}

輸出結果:
父類無參
子類無參
----------
父類無參
子類有參

8.3 小結

  • 子類構造方法執行的時候,都會在第一行預設先呼叫父類無引數構造方法一次。
  • 子類構造方法的第一行都隱含了一個super()去呼叫父類無引數構造方法,super()可以省略不寫。

9 super(...)和this(...)

9.1 引入

請看上節中的如下案例:

class Person {
    private String name;
    private int age;

    public Person() {
        System.out.println("父類無參");
    }

    // getter/setter省略
}

class Student extends Person {
    private double score;

    public Student() {
        //super(); // 呼叫父類無參構造方法,預設就存在,可以不寫,必須再第一行
        System.out.println("子類無參");
    }
    
     public Student(double score) {
        //super();  // 呼叫父類無參構造方法,預設就存在,可以不寫,必須再第一行
        this.score = score;    
        System.out.println("子類有參");
     }
      // getter/setter省略
}

public class Demo07 {
    public static void main(String[] args) {
        // 呼叫子類有引數構造方法
        Student s2 = new Student(99.9);
        System.out.println(s2.getScore()); // 99.9
        System.out.println(s2.getName()); // 輸出 null
        System.out.println(s2.getAge()); // 輸出 0
    }
}

我們發現,子類有引數構造方法只是初始化了自己物件中的成員變數score,而父類中的成員變數name和age依然是沒有資料的,怎麼解決這個問題呢,我們可以藉助與super(...)去呼叫父類構造方法,以便初始化繼承自父類物件的name和age.

9.2 super和this的用法格式

super和this完整的用法如下,其中this,super訪問成員我們已經接觸過了。

this.成員變數        --    本類的
super.成員變數        --    父類的

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

接下來我們使用呼叫構造方法格式:

super(...) -- 呼叫父類的構造方法,根據引數匹配確認
this(...) -- 呼叫本類的其他構造方法,根據引數匹配確認

9.3 super(....)用法演示

程式碼如下:

class Person {
    private String name ="鳳姐";
    private int age = 20;

    public Person() {
        System.out.println("父類無參");
    }
    
    public Person(String name , int age){
        this.name = name ;
        this.age = age ;
    }

    // getter/setter省略
}

class Student extends Person {
    private double score = 100;

    public Student() {
        //super(); // 呼叫父類無參構造方法,預設就存在,可以不寫,必須再第一行
        System.out.println("子類無參");
    }
    
     public Student(String name , int age,double score) {
        super(name ,age);// 呼叫父類有參構造方法Person(String name , int age)初始化name和age
        this.score = score;    
        System.out.println("子類有參");
     }
      // getter/setter省略
}

public class Demo07 {
    public static void main(String[] args) {
        // 呼叫子類有引數構造方法
        Student s2 = new Student("張三",20,99);
        System.out.println(s2.getScore()); // 99
        System.out.println(s2.getName()); // 輸出 張三
        System.out.println(s2.getAge()); // 輸出 20
    }
}

注意:

子類的每個構造方法中均有預設的super(),呼叫父類的空參構造。手動呼叫父類構造會覆蓋預設的super()。

super() 和 this() 都必須是在構造方法的第一行,所以不能同時出現。

super(..)是根據引數去確定呼叫父類哪個構造方法的。

9.4 super(...)案例圖解

父類空間優先於子類物件產生

在每次建立子類物件時,先初始化父類空間,再建立其子類物件本身。目的在於子類物件中包含了其對應的父類空間,便可以包含其父類的成員,如果父類成員非private修飾,則子類可以隨意使用父類成員。程式碼體現在子類的構造七呼叫時,一定先呼叫父類的構造方法。理解圖解如下:

2.jpg

9.5 this(...)用法演示

this(...)

  • 預設是去找本類中的其他構造方法,根據引數來確定具體呼叫哪一個構造方法。
  • 為了借用其他構造方法的功能。
package com.itheima._08this和super呼叫構造方法;
/**
 * this(...):
 *    預設是去找本類中的其他構造方法,根據引數來確定具體呼叫哪一個構造方法。
 *    為了借用其他構造方法的功能。
 *
 */
public class ThisDemo01 {
    public static void main(String[] args) {
        Student xuGan = new Student();
        System.out.println(xuGan.getName()); // 輸出:徐幹
        System.out.println(xuGan.getAge());// 輸出:21
        System.out.println(xuGan.getSex());// 輸出: 男
    }
}

class Student{
    private String name ;
    private int age ;
    private char sex ;

    public Student() {
  // 很弱,我的兄弟很牛逼啊,我可以呼叫其他構造方法:Student(String name, int age, char sex)
        this("徐幹",21,'男');
    }

    public Student(String name, int age, char sex) {
        this.name = name ;
        this.age = age   ;
        this.sex = sex   ;
    }

    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;
    }

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }
}

9.6 小結

  • 子類的每個構造方法中均有預設的super(),呼叫父類的空參構造。手動呼叫父類構造會覆蓋預設的super()。
  • super() 和 this() 都必須是在構造方法的第一行,所以不能同時出現。
  • super(..)和this(...)是根據引數去確定呼叫父類哪個構造方法的。
  • super(..)可以呼叫父類構造方法初始化繼承自父類的成員變數的資料。
  • this(..)可以呼叫本類中的其他構造方法。

10 繼承的特點

  1. Java只支援單繼承,不支援多繼承。

    // 一個類只能有一個父類,不可以有多個父類。
    class A {}
    class B {}
    class C1 extends A {} // ok
    // class C2 extends A, B {} // error
  2. 一個類可以有多個子類。

    // A可以有多個子類
    class A {}
    class C1 extends A {}
    class C2 extends  A {}
  3. 可以多層繼承。

    class A {}
    class C1 extends A {}
    class D extends C1 {}
    頂層父類是Object類。所有的類預設繼承Object,作為父類。

相關文章