用例子理解里氏替換原則:
需求:
(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í)例,如下圖:

當(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)存分配如下:

顧客要買的是寵物,可能是貓,可能是狗,若以顧客希望的是寵物叫,而不是具體的貓叫或狗叫。
此時可以使用父類引用指向子類實(shí)例。
代碼重構(gòu)如下:
public class PetShop{//寵物店
public static void main(String[] args) {
Pet pet = null;
pet = new Dog();//先看看狗,讓狗叫一聲
}
}
此時內(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)存的分配如下:

顧客想看看貓爬樹,如果能上樹就不買了,此時顧客怎么讓貓爬樹呢?
是否可以用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;
原因用圖表示一下

里氏替換是指用子類實(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)用子類從父類繼承的方法。