(三)繼承 與 多態(tài) 簡單介紹Object類

繼承 與 多態(tài) 簡單介紹Object類


一. 繼承

繼承是非常簡單而強大的設(shè)計思想,它提供了我們代碼重用和程序組織的有力工具。

類是規(guī)則,用來制造對象的規(guī)則。我們不斷地定義類,用定義的類制造一些對象。類定義了對象的屬性和行為,就像圖紙決定了房子要蓋成什么樣子。

一張圖紙可以蓋很多房子,它們都是相同的房子,但是坐落在不同的地方,會有不同的人住在里面。假如現(xiàn)在我們想蓋一座新房子,和以前蓋的房子很相似,但是稍微有點不同。任何一個建筑師都會拿以前蓋的房子的圖紙來,稍加修改,成為一張新圖紙,然后蓋這座新房子。所以一旦我們有了一張設(shè)計良好的圖紙,我們就可以基于這張圖紙設(shè)計出很多相似但不完全相同的房子的圖紙來。

基于已有的設(shè)計創(chuàng)造新的設(shè)計,就是面向?qū)ο蟪绦蛟O(shè)計中的繼承。在繼承中,新的類不是憑空產(chǎn)生的,而是基于一個已經(jīng)存在的類而定義出來的。通過繼承,新的類自動獲得了基礎(chǔ)類中所有的成員,包括成員變量和方法,包括各種訪問屬性的成員,無論是public還是private。當然,在這之后,程序員還可以加入自己的新的成員,包括變量和方法。顯然,通過繼承來定義新的類,遠比從頭開始寫一個新的類要簡單快捷和方便。繼承是支持代碼重用的重要手段之一。

類這個詞有分類的意思,具有相似特性的東西可以歸為一類。比如所有的鳥都有一些共同的特性:有翅膀、下蛋等等。鳥的一個子類,比如雞,具有鳥的所有的特性,同時又有它自己的特性,比如飛不太高等等;而另外一種鳥類,比如鴕鳥,同樣也具有鳥類的全部特性,但是又有它自己的明顯不同于雞的特性。

如果我們用程序設(shè)計的語言來描述這個雞和鴕鳥的關(guān)系問題,首先有一個類叫做“鳥”,它具有一些成員變量和方法,從而闡述了鳥所應(yīng)該具有的特征和行為。然后一個“雞”類可以從這個“鳥”類派生出來,它同樣也具有“鳥”類所有的成員變量和方法,然后再加上自己特有的成員變量和方法。無論是從“鳥”那里繼承來的變量和方法,還是它自己加上的,都是它的變量和方法。

繼承 is-a 而不是 has-a //判斷分類樹是否合理 采用is-a測試

1. 繼承的好處

  • 如果無法繼承,程序代碼重復(fù)的太多,一方面代碼冗余,另一方面每次維護都要修改多處極其不方便,即可拓展性差
  • 定義出了共同的協(xié)議(通過繼承來定義相關(guān)類之間的共同協(xié)議)

2. 繼承的概念

  • 我們把用來做基礎(chǔ)派生其它類的那個類叫做父類、超類或者基類(SuperClass),而派生出來的新類叫做子類(ThisClass)。Java用關(guān)鍵字extends表示這種繼承/派生關(guān)系:

    class ThisClass extends SuperClass {
        // ……
    }
    
  • Java的繼承只允許單根繼承,即一個類只能有一個父類

  • Java中,支持多重繼承,爺爺..爸爸..兒子..


3. 重點:子類與父類的關(guān)系 (繼承 訪問 構(gòu)造 this與super)

  1. 哪些東西被繼承了?繼承了是否可以訪問?
  • 答: 繼承了所有成員變量、成員函數(shù)/方法,包括其在父類中的訪問屬性,除了構(gòu)造器

  • 比喻: 繼承 ——得到 訪問——使用

  • 我們不可以在子類中重新定義繼承得到的成員的訪問屬性。

  • 如果我們試圖重新定義一個在父類中已經(jīng)存在的成員變量,那么我們是在定義一個與父類的成員變量完全無關(guān)的變量,在子類中我們可以訪問這個定義在子類中的變量,在父類的方法中訪問父類的那個。盡管它們同名但是互不影響。 (就近原則)

    • 如果父子類有有同名屬性,在子類方法中想要使用父類的屬性值,使用super。
  • 得到不等于可以隨便使用。父類的private的成員在子類里仍然是存在的,只是子類中不能直接訪問。每個成員有不同的訪問屬性,子類繼承得到了父類所有的成員,但是不同的訪問屬性使得子類在使用這些成員時有所不同:有些父類的成員直接成為子類的對外的界面,有些則被深深地隱藏起來,即使子類自己也不能直接訪問。

繼承中的訪問權(quán)限
  1. thissuper

    • this : 當前(子類)對象的引用

      1. 區(qū)分同名的局部變量與屬性
      2. 調(diào)用其他構(gòu)造方法。this(參數(shù)列表)
    • super : 代表當前類的父類的對象的引用

      • super不是對象,而是當前子類的對象有一個區(qū)域 可以指向父類的對象的成員
      1. 區(qū)分子類與父類的成員變量(屬性)
      2. 區(qū)分同名的(覆蓋的)父子類相同方法
      3. 子類中調(diào)用父類的構(gòu)造方法 super(參數(shù)列表)
        • 在子類構(gòu)造方法中調(diào)用父類構(gòu)造方法必須在第一句,而且只能有一個
  2. 子類與父類 構(gòu)造方法與順序

    1. 構(gòu)造順序: 父類初始化模塊 -> 父類構(gòu)造方法 -> 子類初始化模塊 -> 子類構(gòu)造方法

      ( 務(wù)必聯(lián)想 debug時 代碼執(zhí)行的順序)

      class A{
          static {
              System.out.println("父類靜態(tài)代碼塊");    // No.1
          }
          public A(){
              System.out.println("父類構(gòu)造方法");    // No.3
          }
          {
              System.out.println("父類初始化塊");    // No.4
          }
      }
      public class B extends A{
          static{
              System.out.println("子類靜態(tài)代碼塊");    // No.2
          }
          public B(){
              System.out.println("子類構(gòu)造方法");    // No.5
          }
          {
              System.out.println("子類初始化塊");    // No.6
          }
          public static void main(String[] args){
              new B();
          }
      }
      
    2. 構(gòu)造方法:

      1. 當沒有自定義的情況下,jvm會默認分配一個無參構(gòu)造方法

        public ThisClass () {
            super();
        }
        
      2. 在子類構(gòu)造方法中,若沒有調(diào)用其他構(gòu)造方法,第一句默認調(diào)用父類的無參構(gòu)造super()

      3. 若需要給父類子類共同的成員變量構(gòu)造初始化,又給子類特殊的成員變量構(gòu)造初始化,使用super(參數(shù)列表)

        class Item {
            private String title;
            private int playingTime;
            private boolean gotIt = false;
            private String comment;
        
            public Item(String title, int playingTime, boolean gotIt, String comment) {
                this.title = title;
                this.playingTime = playingTime;
                this.gotIt = gotIt;
                this.comment = comment;
            }
        }
        
        public class DVD extends Item {
            private String director;
            public DVD(String title, String director, int playingTime, String comment) {
                super(title, playingTime, false, comment);  // 值得注意
                this.director = director;
            }
        }
        
        • 使用super(參數(shù))調(diào)用父類的構(gòu)造方法,必須在第一句。只能有一個。
  3. 覆蓋/重寫 (override) // 區(qū)分重載(overload)

    重寫是子類對父類的允許訪問的方法的實現(xiàn)過程進行重新編寫, 返回值和形參都不能改變。即外殼不變,核心重寫!

    重寫的好處在于子類可以根據(jù)需要,定義特定于自己的行為。 也就是說子類能夠根據(jù)需要實現(xiàn)父類的方法。

    而重載overload 是在一個類里面,方法名字相同,而參數(shù)列表必須不同。 最常見的是構(gòu)造方法的重載

  • 重寫規(guī)則:
    • 命名與參數(shù)列表必須與被重寫方法完全相同
    • 返回類型必須是父類返回類型的派生類(一般而言都是相同)
    • 訪問權(quán)限不能比父類更低(一般相同 或更高)
    • 父類的方法只能被它的子類覆蓋重寫
    • static不能被重寫,但是可以另外再次聲明(屬于子類靜態(tài)方法)
    • 構(gòu)造方法不能被重寫

4. 繼承的五大設(shè)計步驟

  1. 找到具有共同的屬性和行為的對象
  2. 抽象設(shè)計出代表共同屬性和行為的類
  3. 決定子類是否需要讓某項行為(方法的實現(xiàn))有特定不同的運作方式 // 是否需要覆蓋重寫
  4. 進一步抽象,尋找使用共同行為的子類
  5. 建立繼承樹, 完成類的繼承層次

二. 多態(tài)

類定義了類型,DVD類所創(chuàng)建的對象的類型就是DVD。類可以有子類,所以由那些類定義的類型可以有子類型。在DoME的例子中,DVD類型就是Item類型的子類型。

子類型類似于類的層次,類型也構(gòu)成了類型層次。子類所定義的類型是其超類的類型的子類型。

當把一個對象賦值給一個變量時,對象的類型必須與變量的類型相匹配,如:

Car myCar = new Car();

是一個有效的賦值,因為Car類型的對象被賦值給聲明為保存Car類型對象的變量。但是由于引入 了繼承,這里的類型規(guī)則就得敘述得更完整些:

一個變量可以保存其所聲明的類型或該類型的任何子類型。

對象變量可以保存其聲明的類型的對象,或該類型的任何子類型的對象。

Java中保存對象類型的變量是多態(tài)變量?!岸鄳B(tài)”這個術(shù)語(字面意思是許多形態(tài))是指一個變量可以保存不同類型(即其聲明的類型或任何子類型)的對象。

1. 多態(tài)變量

  • 首先,明確變量與對象的區(qū)別,對象是實體,變量是引用(控制者/遙控器)

  • 類Class定義了類型Type,子類定義了子類型; 子類的對象可以被“當作”父類的對象來使用

    • 賦值給父類的變量(子類對象 賦值給 聲明類型為父類類型的變量)(向上造型)

      Vehicle v1 = new Car();

    • 傳遞給需要父類對象的函數(shù)(子類對象 傳遞給函數(shù)參數(shù)為父類類型的函數(shù))

      public void add(Item item) {}

      base.add(cd)

    • 放進存放父類對象的容器里(子類對象 放入存放對象類型為父類類型的泛型容器)

      ArrayList<Item> 可以存儲 CD, DVD 等子類型的對象

  • 變量的聲明類型 稱為 靜態(tài)類型, 程序運行到這個變量時實際存儲的類型 稱為 動態(tài)類型

  • Java的對象變量都是多態(tài)變量,能保存其所聲明的類型的對象,或者聲明類型的子類型的對象

  • 當把子類的對象賦給父類的變量時,就發(fā)生了向上造型(父類變量 引用 子類對象)

2.向上造型

  • 造型 Cast :靜態(tài)類型的變量 引用了一個 動態(tài)類型與靜態(tài)類型不符合的對象

    • 用括號圍起類型放在值的前面(子類對象與父類引用變量連接 自動向上造型)

    • 對象本身沒有發(fā)生任何變化(所以不是“類型轉(zhuǎn)換”)(實質(zhì)上只是當成什么類型來看待的問題,而不是轉(zhuǎn)變)

    • 運行時有機制來檢查這樣的轉(zhuǎn)化是否合理(Java只支持向上造型,不支持向下造型)否則拋出ClassCastException

  • 向上造型:

    • 只要 父類引用變量(多態(tài)變量) 指向 子類對象實例 那么就發(fā)生向上造型(拿一個子類的對象 當成父類對象來用)

    • 父類不能引用子類中的獨有的屬性或方法。只能由子類重寫override父類中的方法或?qū)傩?/strong>

      (向上造型后 子類 將不再具備其自己定義的方法,只有父類的方法。但若重寫了父類的方法,向上造型的對象的方法為重寫后新的方法)

class Animal {
    public void move() {
        System.out.println("Animals can move.");
    }

//    public void bark() {}
}

class Dog extends Animal {
    public void move() {
        super.move();
        System.out.println("Dog can run and jump.");
    }

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

public class TestDog {
    public static void main(String[] args) {
        Animal a = new Animal();
        Dog d = new Dog();
        Animal m = new Dog();  //向上cast

        m.bark(); // 此時編譯錯誤,除非在Animal中添加bark()方法; 
                  // 添加之后正常運行,輸出 bark!bark! (已override)
        a.move(); 
        d.move(); //override
        d.bark();
        
        Dog dog = new Dog();
        Animal animal = new Animal();
        animal = dog; // 向上造型,沒問題
        dog = animal; // 向下造型,錯誤
        dog = (Dog)animal //強制cast,編譯可以通過,但是由于animal引用的對象實例是Animal,拋出異常
        animal = d;   // 將d向上造型
        dog = animal; // 雖然意義上可行,但是依然屬于向下造型,錯誤
        dog = (Dog)animal;// 強制cast,對象實例符合Dog,合理,正常
    }
}

3. 覆蓋/重寫override 與 綁定

如果子類的方法覆蓋了父類的方法,我們也說父類的那個方法在子類有了新的版本或者新的實現(xiàn)。覆蓋的新版本具有與老版本相同的方法簽名:相同的方法名稱和參數(shù)表。因此,對于外界來說,子類并沒有增加新的方法,仍然是在父類中定義過的那個方法。不同的是,這是一個新版本,所以通過子類的對象調(diào)用這個方法,執(zhí)行的是子類自己的方法。

覆蓋關(guān)系并不說明父類中的方法已經(jīng)不存在了,而是當通過一個子類的對象調(diào)用這個方法時,子類中的方法取代了父類的方法,父類的這個方法被“覆蓋”起來而看不見了。而當通過父類的對象調(diào)用這個方法時,實際上執(zhí)行的仍然是父類中的這個方法。注意我們這里說的是對象而不是變量,因為一個類型為父類的變量有可能實際指向的是一個子類的對象。

當調(diào)用一個方法時,究竟應(yīng)該調(diào)用哪個方法,這件事情叫做綁定。綁定表明了調(diào)用一個方法的時候,我們使用的是哪個方法。綁定有兩種:一種是早綁定,又稱靜態(tài)綁定,這種綁定在編譯的時候就確定了;另一種是晚綁定,即動態(tài)綁定。動態(tài)綁定在運行的時候根據(jù)變量當時實際所指的對象的類型動態(tài)決定調(diào)用的方法。Java缺省使用動態(tài)綁定。

  • 當通過引用變量調(diào)用方法的時候,調(diào)用哪個方法這件事情叫做綁定。

    • 靜態(tài)綁定:根據(jù)引用變量的靜態(tài)類型(聲明類型)來決定
    • 動態(tài)綁定:根據(jù)引用變量的動態(tài)類型(實際指向的對象實例類型)來決定
  • Java默認所有的綁定都是 動態(tài)綁定

  • 所以 向上造型的時候,雖然只能調(diào)用父類有的方法,但是支持override,即只要override那么執(zhí)行override之后的方法

    (既然你是一個Animal,那你應(yīng)該會bark(),那么,你就去bark()吧; 至于如何bark(),看著辦)


三. Object類

  • 所有的沒有extends的類 默認繼承于Object類(Object類是所有類的“上級”)

Object類的方法

  • toString()

    • 返回該對象的地址的String; (除非進行了override,比如String進行了override)
    • System.out.println(); 默認將()里的對象執(zhí)行toString()方法
  • equals()

    • 返回boolean 是否是指向同一個對象實例(String 進行了override 可以判斷等值與否)

附: 簡單介紹:可拓展性 可維護性

可拓展性: 代碼不需要改動或很少改動,就可以擴展特性,適應(yīng)新的內(nèi)容

可維護性: 經(jīng)過修改可以適應(yīng)新的內(nèi)容


@Author: nju_zzp
@Date: 2020/3/16

最后編輯于
?著作權(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)容