文章目錄
- 📑前言
- 一、向上轉型和向下轉型
- 1.1 向上轉型
- 1.2 向下轉型
- 二、多態的優缺點
- 2.1 多態優點
- 2.2 多態缺陷
- 三、避免避免構造方法中調用重寫的方法
- 四、好的習慣
- 🌤?全篇總結
📑前言
在面向對象編程中,向上轉型和向下轉型是常用的技術手段,可以實現不同類之間的轉換和靈活應用。同時,多態作為面向對象編程的重要特性,具有諸多優點和缺陷,對代碼的設計和性能都有一定影響。本文將深入探討向上轉型、向下轉型以及多態的優缺點,幫助讀者更好地理解和運用這些概念在Java編程中的實際應用和注意事項。
一、向上轉型和向下轉型
1.1 向上轉型
實際就是創建一個子類對象,將其當成父類對象來使用。
語法格式:父類類型 對象名 = new 子類類型()
Animal animal = new Cat(“小小”,3);
animal是父類類型,但可以引用一個子類對象,因為是從小范圍向大范圍的轉換。
【使用場景】
- 直接賦值
- 方法傳參
- 方法返回
class TestAnimal {// 2. 方法傳參:形參為父類型引用,可以接收任意子類的對象public static void eatFood(Animal a){a.eat();} // 3. 作返回值:返回任意子類對象public static Animal buyAnimal(String var){if("狗".equals(var) ){return new Dog("狗狗",1);}else if("貓" .equals(var)){return new Cat("貓貓", 1);}else{return null;}}public static void main(String[] args) {// 1. 直接賦值:子類對象賦值給父類對象Animal cat = new Cat("元寶",2); Dog dog = new Dog("小七", 1);eatFood(cat);eatFood(dog);Animal animal = buyAnimal("狗");animal.eat();animal = buyAnimal("貓");animal.eat();}
}
**向上轉型的優點:**讓代碼實現更簡單靈活。
**向上轉型的缺陷:**不能調用到子類特有的方法。
1.2 向下轉型
將一個子類對象經過向上轉型之后當成父類方法使用,再無法調用子類的方法,但有時候可能需要調用子類特有的方法,此時:將父類引用再還原為子類對象即可,即向下轉換。
public static void main(String[] args) {Animal animal1 = new Dog("小七",7);//向上轉型Animal animal2 = new Cat("咪咪",3);//向上轉型animal1 = (Dog)animal1;//向下轉型animal2 = (Cat)animal1;//向下轉型編譯報錯,因為animal1實際上是指向的是Dog,強制轉化為Cat無法還原}
向下轉型用的比較少,而且不安全,萬一轉換失敗,運行時就會拋異常。Java中為了提高向下轉型的安全性,引入了 instanceof ,如果該表達式為true,則可以安全轉換。
二、多態的優缺點
2.1 多態優點
- 提高代碼的可擴展性和可維護性:通過多態,可以將具體的實現與抽象的接口分離,使得系統的各個模塊之間的耦合度降低,從而方便對系統進行擴展和維護。
- 增強代碼的靈活性:通過多態,可以在運行時動態地決定對象的具體類型,從而實現不同對象的不同行為。這樣可以根據實際需求靈活地進行對象的選擇和使用。
- 提高代碼的可讀性和可理解性:通過多態,可以將對象的具體類型隱藏起來,只關注對象的抽象類型和接口,從而使得代碼更加簡潔、清晰,易于理解和閱讀
- 能夠降低代碼的 “圈復雜度”, 避免使用大量的 if - else
擴展:
- 圈復雜度是一種描述一段代碼復雜程度的方式. 一段代碼如果平鋪直敘, 那么就比較簡單容易理解. 而如果有很多的條件分支或者循環語句, 就認為理解起來更復雜.
- 因此我們可以簡單粗暴的計算一段代碼中條件語句和循環語句出現的個數, 這個個數就稱為 “圈復雜度”。如果一個方法的圈復雜度太高, 就需要考慮重構,不同公司對于代碼的圈復雜度的規范不一樣. 一般不會超過 10 。
class Book{public void read(){System.out.println("看書");}
}
class English extends Book{public void read(){System.out.println("看英語書");}
}
class Maths extends Book{public void read(){System.out.println("看數學書");}
}
class Language extends Book{@Overridepublic void read() {System.out.println("看語文書");}
}
public class Exercise3 {public static void readBook(){English english = new English();Maths maths = new Maths();Language language = new Language();Book[] book = {english,language,maths,language,maths,english};for (Book x:book) {x.read();}}public static void main(String[] args) {readBook();}
}
2.2 多態缺陷
性能損失:由于多態需要在運行時進行類型的判斷和方法的動態綁定,所以會帶來一定的性能損失。相比于直接調用具體類型的方法,多態需要進行額外的判斷和查找,從而導致一定的性能下降。
可能引發運行時錯誤:由于多態是在運行時動態決定對象的具體類型,所以如果在使用多態的過程中出現了類型錯誤或者類型轉換錯誤,就會導致運行時錯誤的發生。這就需要在使用多態時進行嚴格的類型檢查和錯誤處理,增加了代碼的復雜性和難度。
可能導致代碼的混亂和難以理解:多態的使用會使得代碼中出現更多的抽象和接口,從而增加了代碼的復雜性和難度。如果使用不當,可能會導致代碼的混亂和難以理解,降低代碼的可讀性和可維護性。因此,在使用多態時需要謹慎設計和使用。
三、避免避免構造方法中調用重寫的方法
class B {public B() {
// do nothingfunc();}public void func() {System.out.println("B.func()");}
}
class D extends B {private int num = 1;@Overridepublic void func() {System.out.println("D.func() " + num);}
}
public class Test {public static void main(String[] args) {D d = new D();}
}
// 執行結果D.func() 0
一段有坑的代碼. 我們創建兩個類, B 是父類, D 是子類. D 中重寫 func 方法. 并且在 B 的構造方法中調用 func。
構造 D 對象的同時, 會調用 B 的構造方法.
B 的構造方法中調用了 func 方法, 此時會觸發動態綁定, 會調用到 D 中的 func
此時 D 對象自身還沒有構造, 此時 num 處在未初始化的狀態, 值為 0. 如果具備多態性,num的值應該是1.
所以在構造函數內,盡量避免使用實例方法,除了final和private方法。
四、好的習慣
“用盡量簡單的方式使對象進入可工作狀態”, 盡量不要在構造器中調用方法(如果這個方法被子類重寫, 就會觸發動態綁定, 但是此時子類對象還沒構造完成), 可能會出現一些隱藏的但是又極難發現的問題。
🌤?全篇總結
向上轉型可以實現子類對象當成父類對象使用,提高代碼的靈活性和復用性;向下轉型則可以讓父類引用還原為子類對象,調用子類特有方法。多態能夠提高代碼的可擴展性和可維護性,但也存在性能損失和潛在的錯誤風險,需要謹慎使用和設計。
避免在構造函數中調用重寫方法,保持構造過程簡潔和清晰,避免潛在的問題。形成良好的編程習慣,能夠提高代碼的質量和可維護性,減少潛在的錯誤和調試成本,是每位Java程序員都應該注意的重要方面。