首頁 > 軟體

Java 繼承與多型超詳細梳理

2022-04-02 13:00:59

一、繼承

1、繼承的概念

繼承機制:是物件導向程式設計是程式碼可以複用的最重要手段,允許程式設計師在保持原有類特性的基礎上進行擴充套件,增加新的功能,產生的新類,成為派生類/子類。繼承主要解決的問題是:共性的抽取,實現程式碼的複用。

2、繼承的語法

表示類與類的繼承關係,需要藉助關鍵字extends,語法如下:

修飾符  class  子類/派生類   extends  父類別/基礎類別/超類{

       //…………

}

  • 子類會將父類別的成員變數或者成員方法繼承到子類中
  • 子類繼承父類別後,必須新增自己特有的成員,體現與基礎類別的不同

3、父類別成員存取

(1)子類中存取父類別的成員變數

  • 不存在同名成員變數時,正常存取就行
  • 存在同名成員變數,使用(super.變數名)實現父類別成員變數的存取
public class Base {
    int a;
    int b;
    int c;
} 
public class Derived extends Base{
    int a; // 與父類別中成員a同名,且型別相同
    char b; // 與父類別中成員b同名,但型別不同
    public void method(){
        a = 100; // 存取父類別繼承的a,還是子類自己新增的a?
        b = 101; // 存取父類別繼承的b,還是子類自己新增的b?
        c = 102; // 子類沒有c,存取的肯定是從父類別繼承下來的c
    }
}
  • 存取成員變數時,優先存取自己的成員變數。即同名成員變數存取時,優先存取子類的。即:子類將父類別的成員隱藏了
  • 成員變數存取遵循就近原則,自己有優先自己的,自己沒有則向父類別中查詢。

(2)子類中存取父類別的成員方法

  • 成員方法名字不同,正常存取即可
  • 成員方法名字相同,可以通過 【super.方法名】存取同名父類別方法

如果父類別和子類同名方法的參數列不同(過載),根據呼叫方法時傳遞的引數選擇合適的方法存取。

如果父類別和子類的同名方法原型一致,則存取子類的

4、super關鍵字

super關鍵字的主要作用是:在子類方法中存取父類別的同名成員。(只能在非靜態方法中使用)

public class Base {
    int a;
    int b;
    public void methodA(){
        System.out.println("Base中的methodA()");
    }
    public void methodB(){
        System.out.println("Base中的methodB()");
}
public class Derived extends Base{
    int a; 
    char b; 
    // 與父類別中methodA()構成過載
    public void methodA(int a) {
        System.out.println("Derived中的method()方法");
    }
    // 與父類別中methodB()構成重寫
    public void methodB(){
        System.out.println("Derived中的methodB()方法");
    }
    public void methodC(){
        a = 100; // 等價於: this.a = 100;
        b = 101; // 等價於: this.b = 101;
        // 存取父類別的成員變數時,需要藉助super關鍵字
        // super是獲取到子類物件中從基礎類別繼承下來的部分
        super.a = 200;
        super.b = 201;
        methodA(); // 沒有傳參,存取父類別中的methodA()
        methodA(20); // 傳遞int引數,存取子類中的methodA(int)
        methodB(); // 直接存取,則永遠存取到的都是子類中的methodA(),基礎類別的無法存取到
        super.methodB(); // 存取基礎類別的methodB()
    }
}

5、子類構造方法

子類物件構造時,需要先呼叫父類別的構造方法,然後執行子類的構造方法。

public class Base {
    public Base(){
        System.out.println("Base()");
    }
}
public class Derived extends Base{
    public Derived(){
   // super(); // 注意子類構造方法中預設會呼叫基礎類別的無參構造方法:super(),
   // 使用者沒有寫時,編譯器會自動新增,而且super()必須是子類構造方法中第一條語句,
   // 並且只能出現一次
        System.out.println("Derived()");
    }
}
  • 若父類別顯示定義無參或者預設的建構函式,在子類的構造方法第一行預設有隱含的super()呼叫。
  • 父類別定義帶引數的構造方法時,編譯器不會再給子類生成預設的構造方法,需要子類顯式定義,並在子類構造方法中呼叫合適的父類別構造方法
  • 子類構造方法中,super(……)呼叫父類別構造方法,必須是子類構造方法的第一條語句
  • super(……)只能在子類構造方法中出現一次,並且不能和this同時出現

6、super和this

super和this都可以在成員方法中用來存取成員變數和呼叫其他的成員函數,都可以作為構造方法的第一條語句,那麼它們之間的區別是什麼?

(1)相同點

  • 都是java的關鍵字
  • 只能在類的非靜態方法中使用,用來存取非靜態成員方法和屬性
  • 必須作為構造方法中的第一條語句,並且不能同時存在

(2)不同點

  • this是當前物件的參照,super是子類物件中從父類別繼承的成員的參照
  • this是非靜態成員方法的一個隱藏引數,super不是隱藏引數
  • 在構造方法中:this()用於呼叫本類的構造方法,super()用來呼叫父類別構造方法,兩種呼叫不能同時出現在構造方法中
  • 子類的構造方法中一定會存在super()的呼叫,但是this()使用者不寫就沒有

7、程式碼塊執行順序

【普通類】

  • 靜態程式碼塊先執行,並且只執行一次,在類載入階段執行
  • 當有物件建立時,才會執行範例程式碼塊,最後執行構造方法

【繼承關係上的執行順序】

  • 父類別靜態程式碼塊優先於子類靜態程式碼塊執行,最早執行
  • 父類別範例程式碼塊和父類別構造方法緊接著執行
  • 子類的範例程式碼塊和構造方法最後執行
  • 第二次範例化子類物件時,父類別和子類的靜態程式碼塊不會再執行

8、繼承方式

【注】Java中不支援多繼承

  • super只能指代直接父類別
  • 繼承關係一般不超過三層

9、final關鍵字

  • 修飾變數時,表示常數(不能修改)
  • 修飾類:此類不能被繼承
  • 修飾方法:表示方法不能被重寫

10、繼承和組合

組合和繼承都能實現程式碼的複用。組合沒有涉及到特殊的語法(如extend關鍵字),僅僅是將一個類的範例作為另一個類的屬性。

  • 繼承表示物件與物件之間是is-a的關係
  • 組合表示物件與物件之間是has-a的關係

一般建議:能用組合儘量用組合

二、多型

1、向上轉型

通過父類別型別的參照呼叫子類物件,向上轉型是安全的

【發生向上轉型的時機】

  • 直接賦值
  • 方法傳參
  • 函數的返回值
public class TestAnimal {
    // 2. 函數傳參:形參為父類別參照,可以接收任意子類的物件
    public static void eatFood(Animal a) {
        a.eat();
    }
 
    // 3. 作返回值:返回任意子類物件
    public static Animal buyAnimal(String var) {
        if ("狗" == var) {
            return new Dog("狗狗", 1);
        } else if ("貓" == var) {
            return new Cat("貓貓", 1);
        } else {
            return null;
        }
    }
 
    public static void main(String[] args) {
        Animal cat = new Cat("元寶", 2); // 1. 直接賦值:子類物件賦值給父類別物件
        Dog dog = new Dog("小七", 1);
    }
}

優缺點:

  • 優點:讓程式碼更加靈活
  • 缺點:不能存取到子類特有的方法

2、重寫

函數名相同、參數列相同、返回值相同或是【協變型別】(父子類關係)

【方法重寫的規則】

  • 重寫的方法存取許可權不能比父類別中原方法的的許可權低;
  • 父類別中被static、private、final修飾的方法、構造方法不能被重寫;
  • 重寫的方法,可以使用 @override 註解來顯示指定(幫助我們進行一些合法性的檢驗)。比如方法名拼寫錯誤,編譯會報錯;
  • 重寫的返回值型別可以不同,但是必須具有父子關係。
  • 被final修飾的方法,叫做密封方法,該方法不能被重寫。
  • 外部類只能是public或者預設許可權

【動態繫結和靜態繫結】

  • 動態繫結:發生的條件(1、父類別參照參照子類物件;2、通過父類別參照,可以存取到子類中的方法)。後期繫結,即在編譯時不能確定方法的行為,需要等到程式執行時,才能夠確定呼叫哪個類的方法;
  • 靜態繫結:前期繫結,編譯時,根據使用者傳遞的引數型別確定具體的呼叫方法(函數過載)

3、多型

一個參照呼叫同一個方法,可以表現出不同的形式,這種思想稱為多型。在父類別的構造方法中不要呼叫重寫的方法。

【多型實現的條件】

  • 必須在繼承條件下
  • 子類對父類別方法進行重寫
  • 通過父類別參照呼叫重寫的方法
  • 發生了向上轉型
public class Animal(){
    String name;
    int age;
    public Animal(String name, int age){
        this.name = name;
        this.age = age;
    }
    public void eat(){
        System.out.println(name + "吃飯");
        }
}
public class Cat extends Animal{
    public Cat(String name, int age){
        super(name, age);
    }
    @Override
    public void eat(){
        System.out.println(name+"吃魚~~~");
    }
}
public class Dog extends Animal {
    public Dog(String name, int age){
        super(name, age);
    }
    @Override
    public void eat(){
        System.out.println(name+"吃骨頭~~~");
    }
}
public class TestAnimal {
    // 編譯器在編譯程式碼時,並不知道要呼叫Dog 還是 Cat 中eat的方法
   // 等程式執行起來後,形參a參照的具體物件確定後,才知道呼叫那個方法
   // 注意:此處的形參型別必須時父類別型別才可以
    public static void eat(Animal a){
        a.eat();
    }
    public static void main(String[] args) {
        Animal animal1 = new Cat("元寶",2);
        Animal animal2 = new Dog("小七", 1);
        eat(animal1);
        eat(animal2);
    }
}

【注】Java中所有的類預設繼承Object類

到此這篇關於Java 繼承與多型超詳細梳理的文章就介紹到這了,更多相關Java 繼承與多型內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com