1. 程式人生 > 其它 >方法覆蓋和多型

方法覆蓋和多型

方法覆蓋和多型

方法覆蓋

1.什麼時候考慮使用方法覆蓋?

  • 父類中的方法無法滿足子類的業務需求,子類有必要對繼承過來的方法進行覆蓋。

  • 方法覆蓋又叫做:方法重寫(重新編寫),英語單詞叫做:Override、Overwrite。

比較常見的:方法覆蓋、方法重寫、override

2.什麼條件滿足的時候構成方法覆蓋?

第一:有 繼承 關係的兩個類

第二:具有 相同方法名、返回值型別、形式引數列表

第三:訪問許可權不能更低,,可以更高。

第四:丟擲異常不能更多,可以更少。(原因)

訪問許可權修飾符許可權從高到低排列是:public ,protected ,預設(就是不寫) , private

這裡還有幾個注意事項:

注意1:方法覆蓋只是針對於 方法,和屬性無關。

注意2:私有方法 無法覆蓋。

注意3:構造方法 不能被繼承,所以 構造方法 也不能被覆蓋。

注意4:方法覆蓋只是針對於“例項方法”,“靜態方法覆蓋”沒有意義。

3.關於Object類中toString()方法的覆蓋?

  • toString()方法存在的作用就是:將java物件轉換成字串形式。

  • 大多數的java類toString()方法都是需要覆蓋的。因為Object類中提供的toString()方法輸出的是一個java物件的記憶體地址。

  • 至於toString()方法具體怎麼進行覆蓋?

格式可以自己定義,或者聽需求的。(聽專案要求的。)

4.方法過載和方法覆蓋有什麼區別?

4.1什麼時候用?

  • 方法過載:當在一個類當中,如果功能相似的話,建議將名字定義的一樣,這樣程式碼美觀,並且方便程式設計。

  • 方法覆蓋:父類中的方法無法滿足子類的業務需求,子類有必要對繼承過來的方法進行覆蓋。

4.2位置?

  • 方法過載:發生在同一個類當中。

  • 方法覆蓋:是發生在具有繼承關係的父子類之間。

4.3二者區別

  • 方法過載:是同一個類中,方法名相同,引數列表不同(個數、順序、型別)。

  • 方法覆蓋:是具有繼承關係的父子類,並且重寫之後的方法必須和之前的方法一致:方法名一致、引數列表一致、返回值型別一致。

重要結論:

當子類對父類繼承過來的方法進行“方法覆蓋”之後,子類物件呼叫該方法的時候,一定執行覆蓋之後的方法。

注意:

最好將父類中的方法原封不動的複製過來。(不建議手動編寫)。

方法覆蓋,就是將繼承過來的那個方法給覆蓋掉了。繼承過來的方法沒了。

eg.

class Animal{
public void move(){
System.out.println("動物在移動!");
}

public void sing(int i){
System.out.println("Animal sing....");
}
}

class Bird extends Animal{
// 對move方法進行方法覆蓋,方法重寫,override
// 最好將父類中的方法原封不動的複製過來。(不建議手動編寫)
// 方法覆蓋,就是將繼承過來的那個方法給覆蓋掉了。繼承過來的方法沒了。
public void move(){
System.out.println("鳥兒在飛翔!!!");
}

//protected表示受保護的。沒有public開放。
// 錯誤:正在嘗試分配更低的訪問許可權; 以前為public
/*
protected void move(){
System.out.println("鳥兒在飛翔!!!");
}
*/

//錯誤:被覆蓋的方法未丟擲Exception
/*
public void move() throws Exception{
System.out.println("鳥兒在飛翔!!!");
}
*/

// 分析:這個sing()和父類中的sing(int i)有沒有構成方法覆蓋呢?
// 沒有,原因是,這兩個方法根本就是兩個完全不同的方法。
// 可以說這兩個方法構成了方法過載嗎?可以。
public void sing(){
System.out.println("Bird sing.....");
}
}

注意:

// 分析:這個sing()和父類中的sing(int i)有沒有構成方法覆蓋呢?
// 沒有,原因是,這兩個方法根本就是兩個完全不同的方法。
// 可以說這兩個方法構成了方法過載嗎?可以。
Animal類的
public void sing(int i){
System.out.println("Animal sing....");
}
和Bird類的
public void sing(){
System.out.println("Bird sing.....");
}
沒有構成方法覆蓋,但是構成方法過載。

多型

1、向上轉型和向下轉型的概念。

  • 向上轉型:子--->父 (upcasting)

又被稱為自動型別轉換:Animal a = new Cat();

  • 向下轉型:父--->子 (downcasting)

又被稱為強制型別轉換:Cat c = (Cat)a; 需要新增強制型別轉換符。

  • 什麼時候需要向下轉型?

需要呼叫或者執行子類物件中特有的方法。

必須進行向下轉型,才可以呼叫。

eg.

// 分析這個程式能否編譯和執行呢?
// 分析程式一定要分析編譯階段的靜態繫結和執行階段的動態繫結。
// 只有編譯通過的程式碼才能執行。沒有編譯,根本輪不到執行。
// 錯誤: 找不到符號
// why??? 因為編譯器只知道a5的型別是Animal,去Animal.class檔案中找catchMouse()方法
// 結果沒有找到,所以靜態繫結失敗,編譯報錯。無法執行。(語法不合法。)
//a5.catchMouse();

// 假設程式碼寫到了這裡,我非要呼叫catchMouse()方法怎麼辦?
// 這個時候就必須使用“向下轉型”了。(強制型別轉換)
// 以下這行程式碼為啥沒報錯????
// 因為a5是Animal型別,轉成Cat,Animal和Cat之間存在繼承關係。所以沒報錯。
Cat x = (Cat)a5;
x.catchMouse(); //貓正在抓老鼠!!!!
  • 向下轉型有風險嗎?

容易出現ClassCastException(型別轉換異常)

eg.

// 向下轉型有風險嗎?
Animal a6 = new Bird(); //表面上a6是一個Animal,執行的時候實際上是一隻鳥兒。
/*
分析以下程式,編譯報錯還是執行報錯???
Cat y = (Cat)a6;
y.catchMouse();

編譯器檢測到a6這個引用是Animal型別,
而Animal和Cat之間存在繼承關係,所以可以向下轉型。
編譯沒毛病。

執行階段,堆記憶體實際建立的物件是:Bird物件。
在實際執行過程中,拿著Bird物件轉換成Cat物件
就不行了。因為Bird和Cat之間沒有繼承關係。

執行是出現異常,這個異常和空指標異常一樣非常重要,也非常經典:
java.lang.ClassCastException:型別轉換異常。
java.lang.NullPointerException:空指標異常。這個也非常重要。
*/

  • 怎麼避免這個風險?

instanceof運算子,可以在程式執行階段動態的判斷某個引用指向的物件是否為某一種型別。

養成好習慣,向下轉型之前一定要使用instanceof運算子進行判斷。

eg.

// 怎麼避免ClassCastException異常的發生???
/*
新的內容,運算子:
instanceof (執行階段動態判斷)
第一:instanceof可以在執行階段動態判斷引用指向的物件的型別。
第二:instanceof的語法:
(引用 instanceof 型別)
第三:instanceof運算子的運算結果只能是:true/false
第四:c是一個引用,c變數儲存了記憶體地址,指向了堆中的物件。
假設(c instanceof Cat)為true表示:
c引用指向的堆記憶體中的java物件是一個Cat。
假設(c instanceof Cat)為false表示:
c引用指向的堆記憶體中的java物件不是一個Cat。

程式設計師要養成一個好習慣:
任何時候,任何地點,對型別進行向下轉型時,一定要使用
instanceof 運算子進行判斷。(java規範中要求的。)
這樣可以很好的避免:ClassCastException
*/
System.out.println(a6 instanceof Cat); //false

if(a6 instanceof Cat){ // 如果a6是一隻Cat
Cat y = (Cat)a6; // 再進行強制型別轉換
y.catchMouse();
}
  • 不管是向上轉型還是向下轉型,首先他們之間必須有繼承關係,這樣編譯器就不會報錯。

注意:以後在工作過程中,和別人聊天時要專業一些,說向上轉型和向下轉型,不要說自動型別轉換,也不要說強制型別轉換,因為自動型別轉換和強制型別轉換是使用在基本資料型別方面的,在引用型別轉換這裡只有向上轉型和向下轉型。

2、什麼是多型?

多種形態,多種狀態,編譯和執行有兩個不同的狀態。

1.編譯期叫做 靜態繫結

2.執行期叫做 動態繫結

eg.分析以下程式碼:

Animal a = new Cat();
a.move();

先來分析編譯階段:

編譯的時候編譯器發現a的型別是Animal,所以編譯器在檢查語法的時候,會去Animal.class位元組碼檔案中找move()方法,找到了,繫結上move()方法,編譯通過。(靜態繫結)

再來分析執行階段:

但是執行的時候,和底層堆記憶體當中的實際物件有關,真正執行的時候會自動呼叫“堆記憶體中真實物件”的相關方法。(動態繫結)

  • 多型的典型程式碼:父型別的引用指向子型別的物件。

eg.

public class Test01{
public static void main(String[] args){
Animal a1 = new Animal();
a1.move(); //動物在移動!!!
Cat c1 = new Cat();
c1.move(); //cat走貓步!
Bird b1 = new Bird();
b1.move(); //鳥兒在飛翔!!!

// 程式碼可以這樣寫嗎?
/*
1、Animal和Cat之間有繼承關係嗎?有的。
2、Animal是父類,Cat是子類。
3、Cat is a Animal,這句話能不能說通?能。
4、經過測試得知java中支援這樣的一個語法:
父型別的引用允許指向子型別的物件。
Animal a2 = new Cat();
a2就是父型別的引用。
new Cat()是一個子型別的物件。
允許a2這個父型別引用指向子型別的物件。
*/
Animal a2 = new Cat();
Animal a3 = new Bird();

// 沒有繼承關係的兩個型別之間存在轉型嗎?
// 錯誤: 不相容的型別: Dog無法轉換為Animal
// Animal a4 = new Dog();
// 呼叫a2的move()方法

/*
什麼是多型?
多種形態,多種狀態。
分析:a2.move();
java程式分為編譯階段和執行階段。
先來分析編譯階段:
對於編譯器來說,編譯器只知道a2的型別是Animal,
所以編譯器在檢查語法的時候,會去Animal.class
位元組碼檔案中找move()方法,找到了,繫結上move()
方法,編譯通過,靜態繫結成功。(編譯階段屬於靜態繫結。)

再來分析執行階段:
執行階段的時候,實際上在堆記憶體中建立的java物件是
Cat物件,所以move的時候,真正參與move的物件是一隻貓,
所以執行階段會動態執行Cat物件的move()方法。這個過程
屬於執行階段繫結。(執行階段繫結屬於動態繫結。)

多型表示多種形態:
編譯的時候一種形態。
執行的時候另一種形態。
*/
a2.move(); //cat走貓步!

// 呼叫a3的move()方法
a3.move(); //鳥兒在飛翔!!!
// ======================================================================
Animal a5 = new Cat(); // 底層物件是一隻貓。
// 分析這個程式能否編譯和執行呢?
// 分析程式一定要分析編譯階段的靜態繫結和執行階段的動態繫結。
// 只有編譯通過的程式碼才能執行。沒有編譯,根本輪不到執行。
// 錯誤: 找不到符號
// why??? 因為編譯器只知道a5的型別是Animal,去Animal.class檔案中找catchMouse()方法
// 結果沒有找到,所以靜態繫結失敗,編譯報錯。無法執行。(語法不合法。)
//a5.catchMouse();

// 假設程式碼寫到了這裡,我非要呼叫catchMouse()方法怎麼辦?
// 這個時候就必須使用“向下轉型”了。(強制型別轉換)
// 以下這行程式碼為啥沒報錯????
// 因為a5是Animal型別,轉成Cat,Animal和Cat之間存在繼承關係。所以沒報錯。
Cat x = (Cat)a5;
x.catchMouse(); //貓正在抓老鼠!!!!

// 向下轉型有風險嗎?
Animal a6 = new Bird(); //表面上a6是一個Animal,執行的時候實際上是一隻鳥兒。

/*
分析以下程式,編譯報錯還是執行報錯???
編譯器檢測到a6這個引用是Animal型別,
而Animal和Cat之間存在繼承關係,所以可以向下轉型。
編譯沒毛病。

執行階段,堆記憶體實際建立的物件是:Bird物件。
在實際執行過程中,拿著Bird物件轉換成Cat物件
就不行了。因為Bird和Cat之間沒有繼承關係。

執行是出現異常,這個異常和空指標異常一樣非常重要,也非常經典:
java.lang.ClassCastException:型別轉換異常。
java.lang.NullPointerException:空指標異常。這個也非常重要。
*/
//Cat y = (Cat)a6;
//y.catchMouse();

// 怎麼避免ClassCastException異常的發生???
/*
新的內容,運算子:
instanceof (執行階段動態判斷)
第一:instanceof可以在執行階段動態判斷引用指向的物件的型別。
第二:instanceof的語法:
(引用 instanceof 型別)
第三:instanceof運算子的運算結果只能是:true/false
第四:c是一個引用,c變數儲存了記憶體地址指向了堆中的物件。
假設(c instanceof Cat)為true表示:
c引用指向的堆記憶體中的java物件是一個Cat。
假設(c instanceof Cat)為false表示:
c引用指向的堆記憶體中的java物件不是一個Cat。

程式設計師要養成一個好習慣:
任何時候,任何地點,對型別進行向下轉型時,一定要使用
instanceof 運算子進行判斷。(java規範中要求的。)
這樣可以很好的避免:ClassCastException
*/

System.out.println(a6 instanceof Cat); //false
if(a6 instanceof Cat){ // 如果a6是一隻Cat
Cat y = (Cat)a6; // 再進行強制型別轉換
y.catchMouse();
}
}
}

3、多型在開發中有什麼作用?

非常重要:五顆星。。。。(多型你會天天用,到處用!!!!)

多型在開發中的作用是:

降低程式的耦合度,提高程式的擴充套件力。

public class Master{
public void feed(Dog d){}
public void feed(Cat c){}
}

以上的程式碼中表示:Master和Dog以及Cat的關係很緊密(耦合度高)。導致擴充套件力很差。

public class Master{
public void feed(Pet pet){
pet.eat();
}
}

以上的代表中表示:Master和Dog以及Cat的關係就脫離了,Master關注的是Pet類。

這樣Master和Dog以及Cat的耦合度就降低了,提高了軟體的擴充套件性。

面向物件的三大特徵:

封裝、繼承、多型

真的是一環扣一環。

有了封裝,有了這種整體的概念之後。

物件和物件之間產生了繼承。

有了繼承之後,才有了方法的覆蓋和多型。

這裡提到了一個軟體開發原則:

七大原則最基本的原則:OCP(對擴充套件開放,對修改關閉)

目的是:降低程式耦合度,提高程式擴充套件力。

面向抽象程式設計,不建議面向具體程式設計。

4、解釋之前遺留的問題

  • 私有方法無法覆蓋。

  • 方法覆蓋只是針對於“例項方法”,“靜態方法覆蓋”沒有意義。(這是因為方法覆蓋通常和多型聯合起來)

1、方法覆蓋需要和多型機制聯合起來使用才有意義。

Animal a = new Cat();
a.move();
  • 要的是什麼效果?

編譯的時候move()方法是Animal的。

執行的時候自動呼叫到子類重寫move()方法上。

  • 假設沒有多型機制,只有方法覆蓋機制,你覺得有意義嗎?

沒有多型機制的話,方法覆蓋可有可無。

沒有多型機制,方法覆蓋也可以沒有,如果父類的方法無法滿足

子類業務需求的時候,子類完全可以定義一個全新的方法。

  • 方法覆蓋和多型不能分開。

2、靜態方法存在方法覆蓋嗎?

多型自然就和物件有關係了。

而靜態方法的執行不需要物件。

所以,一般情況下,我們會說靜態方法“不存在”方法覆蓋。

不探討靜態方法的覆蓋。

eg.

public class OverrideTest05{
public static void** main(String[] args){
// 靜態方法可以使用“引用.”來呼叫嗎?可以
// 雖然使用“引用.”來呼叫,但是和物件無關。
Animal a = **new** Cat(); //多型
// 靜態方法和物件無關。
// 雖然使用“引用.”來呼叫。但是實際執行的時候還是:Animal.doSome()
a.doSome();
Animal.doSome();
Cat.doSome();
}
}

class Animal{
// 父類的靜態方法
public static void doSome(){
System.out.println("Animal的doSome方法執行!");
}
}

class Cat extends Animal{
// 嘗試在子類當中對父類的靜態方法進行重寫
public static void doSome(){
System.out.println("Cat的doSome方法執行!");
}
}

總結兩句話:

私有不能覆蓋。

靜態不談覆蓋。

  • 學習了多型機制之後:

在方法覆蓋中,關於方法的返回值型別。

什麼條件滿足之後,會構成方法的覆蓋呢?

1、發生具有繼承關係的兩個類之間。

2、父類中的方法和子類重寫之後的方法:

具有相同的方法名、相同的形式引數列表、相同的返回值型別

“相同的返回值型別”可以修改一下嗎?

對於返回值型別是基本資料型別來說,必須一致。

對於返回值型別是引用資料型別來說,重寫之後返回值型別可以變的更小(但意義不大,實際開發中沒人這樣寫。)。