里氏替換原則(父類引用指向子類實(shí)例)

用例子理解里氏替換原則:
需求:

(1)設(shè)計寵物類,貓類,狗類,讓貓和狗繼承寵物類

(2)在寵物類中定義sound方法,表示寵物的叫聲,但是叫聲不能由具體的行為。

(3)貓和狗重寫父類的sound方法,以實(shí)現(xiàn)具體的叫聲

代碼如下:

class Pet {//寵物
    public void sound() throws RuntimeException{

    }
}
class Dog extends Pet {//狗

    //方法重寫
    @Override
    public void sound() {
        System.out.println("汪汪汪");
    }
}
class Cat extends Pet{//貓
    //方法重寫
    @Override
    public void sound() throws RuntimeException{
        System.out.println("喵喵喵");
    }
}

開一個寵物店,創(chuàng)建出要賣的貓和狗。顧客在購買寵物時要求寵物叫一聲,以此判斷是否買它。

寵物店代碼如下:

public class PetShop{//寵物店
    public static void main(String[] args) {
        Dog dog = new Dog();
    }
}

此時第3行的代碼運(yùn)行時,內(nèi)存中分配了狗的實(shí)例,如下圖:

內(nèi)存圖

當(dāng)寵物店中代碼中

Dog dog = new Dog();

這樣寫就只能賣狗了。

如果顧客還想看看貓,怎么辦?再new個貓

public class PetShop{//寵物店
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
    }
}

此時內(nèi)存分配如下:


內(nèi)存圖

顧客要買的是寵物,可能是貓,可能是狗,若以顧客希望的是寵物叫,而不是具體的貓叫或狗叫。

此時可以使用父類引用指向子類實(shí)例

代碼重構(gòu)如下:

public class PetShop{//寵物店
    public static void main(String[] args) {
        Pet pet = null;
        pet = new Dog();//先看看狗,讓狗叫一聲
    }
}

此時內(nèi)存是什么樣子?


內(nèi)存圖

如果此時pet引用調(diào)用sound方法,讓寵物叫,那pet調(diào)用的是Dog里的sound?還是調(diào)用Pet里的sound呢?

    public static void main(String[] args) {
        Pet pet = null;
        pet = new Dog();//先看看狗,讓狗叫一聲
        pet.sound();//pet調(diào)用sound,讓寵物叫,實(shí)際上調(diào)用的是Dog的sound方法
    }

運(yùn)行結(jié)果:
汪汪汪

運(yùn)行結(jié)果說明:

  • 父類引用pet 賦值時 賦的是子類對象Dog的實(shí)例。就是這行代碼 pet = new Dog();

  • 我們發(fā)現(xiàn) pet = new Dog(); 這行代碼 = 的右側(cè) new的是子類的實(shí)例,而 = 左側(cè) 是父類的引用。我們把這種情況稱為“父類引用指向子類實(shí)例”

當(dāng)父類引用指向子類實(shí)例時,父類引用可以調(diào)用子類的方法,例如pet可以調(diào)用Dog的sound方法,狗叫了。

那如果顧客現(xiàn)在有看見貓了,向讓貓叫一下。怎么辦?

由于父類引用可以調(diào)用子類的方法,所以現(xiàn)在可以讓pet指向到Cat實(shí)例上去,然后pet調(diào)用sound,調(diào)用的就是Cat的sound方法。

代碼修改如下:

    public static void main(String[] args) {
        Pet pet = null;
        pet = new Dog();//先看看狗,讓狗叫一聲
        pet.sound();//pet調(diào)用sound,讓寵物叫,其實(shí)是狗叫

        pet = new Cat();
        pet.sound();//pet調(diào)用sound,讓寵物叫,其實(shí)是貓叫
    }

運(yùn)行結(jié)果:
汪汪汪
喵喵喵

此時內(nèi)存的分配如下:


內(nèi)存圖

顧客想看看貓爬樹,如果能上樹就不買了,此時顧客怎么讓貓爬樹呢?

是否可以用pet對象調(diào)用climb方法,如

    public static void main(String[] args) {
        Pet pet = null;
        pet = new Dog();
        pet.sound();

        pet = new Cat();
        pet.sound();
//        pet.climb();//報錯
    }

這是不行的,代碼報錯。

為什么呢?

因?yàn)楦割愐谜{(diào)用方法時,必須知道子類有哪些方法,知道的才能調(diào)用,不知道的是不能調(diào)用的。

子類Cat新增的climb()方法父類并不知道,但是父類一定知道子類從父類繼承的方法。

所以父類引用只能調(diào)用子類與父類保持繼承關(guān)系的方法??梢允侵貙懙姆椒ā?/strong>

一種特殊的情況的說明(子類引用指向父類實(shí)例):

下面的代碼是否正確:

    public static void main(String[] args) {
        pet pet = null;
        Dog dog = pet;//報錯: 子類引用dog指向父類實(shí)例
    }

子類引用指向父類實(shí)例報錯,原因用內(nèi)存圖說明吧

  • 當(dāng)子類指向父類引用時,子類會丟失數(shù)據(jù),因此不允許轉(zhuǎn)換。
  • 如果非要轉(zhuǎn),就要強(qiáng)轉(zhuǎn),認(rèn)可丟失的數(shù)據(jù)。Dog dog = (Dog)pet;

原因用圖表示一下

內(nèi)存圖

里氏替換是指用子類實(shí)例替換父類實(shí)例,這種替換叫做里氏替換

Pet pet = new Pet();
Pet pet = new Dog();

里氏替換原則中的調(diào)用規(guī)則:

  • 子類賦給父類(父類引用指向子類實(shí)例),允許
  • 父類賦給子類(子類引用指向父類實(shí)例),強(qiáng)轉(zhuǎn)

里氏替換中的調(diào)用規(guī)則

  • 父類引用只能調(diào)用子類從父類繼承的方法。
  • 當(dāng)子類重寫父類方法后,父類引用調(diào)用的是子類重寫的方法,否則調(diào)用子類從父類繼承的方法。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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