【詳識JAVA語言】面向對象程序三大特性之三:多態

多態

多態的概念

多態的概念:通俗來說,就是多種形態,具體點就是去完成某個行為,當不同的對象去完成時會產生出不同的狀態

多態實現條件?

在java中要實現多態,必須要滿足如下幾個條件,缺一不可:

1. 必須在繼承體系下

2. 子類必須要對父類中方法進行重寫

3. 通過父類的引用調用重寫的方法

多態體現:在代碼運行時,當傳遞不同類對象時,會調用對應類中的方法。

public class Animal { String name; int age;public Animal(String name, int age){ this.name = name; this.age = age; }public void eat(){ System.out.println(name + "吃飯"); }}public class Cat extends Animal{public Cat(String name, int age){ super(name, age); }@Override public void eat(){ System.out.println(name+"吃魚~~~"); }}public class Dog extends Animal {public Dog(String name, int age){ super(name, age); }@Override public void eat(){ System.out.println(name+"吃骨頭~~~"); }}///分割線//public class TestAnimal {// 編譯器在編譯代碼時,并不知道要調用Dog 還是 Cat 中eat的方法 // 等程序運行起來后,形參a引用的具體對象確定后,才知道調用那個方法 // 注意:此處的形參類型必須時父類類型才可以public static void eat(Animal a){ a.eat();}public static void main(String[] args) { Cat cat = new Cat("元寶",2); Dog dog = new Dog("小七", 1);eat(cat); eat(dog);}}運行結果: 元寶吃魚~~~ 元寶正在睡覺 小七吃骨頭~~~ 小七正在睡覺

在上述代碼中, 分割線上方的代碼是 類的實現者 編寫的, 分割線下方的代碼是 類的調用者 編寫的. 當類的調用者在編寫 eat 這個方法的時候, 參數類型為 Animal (父類), 此時在該方法內部并不知道, 也不關注當前的 a 引用指向的是哪個類型(哪個子類)的實例. 此時 a這個引用調用 eat方法可能會有多種不同的表現(和 a 引用的實例 相關), 這種行為就稱為 多態.

重寫

重寫(override):也稱為覆蓋。重寫是子類對父類非靜態、非private修飾,非?nal修飾,非構造方法等的實現過程 進行重新編寫, 返回值和形參都不能改變。即外殼不變,核心重寫!重寫的好處在于子類可以根據需要,定義特定 于自己的行為。 也就是說子類能夠根據需要實現父類的方法。

【方法重寫的規則】

子類在重寫父類的方法時,一般必須與父類方法原型一致: 返回值類型 方法名 (參數列表) 要完全一致

被重寫的方法返回值類型可以不同,但是必須是具有父子關系的

訪問權限不能比父類中被重寫的方法的訪問權限更低。例如:如果父類方法被public修飾,則子類中重寫該方 法就不能聲明為 protected

父類被static、private修飾的方法、構造方法都不能被重寫。

重寫的方法, 可以使用 @Override 注解來顯式指定. 有了這個注解能幫我們進行一些合法性校驗. 例如不小心 將方法名字拼寫錯了 (比如寫成 aet), 那么此時編譯器就會發現父類中沒有 aet 方法, 就會編譯報錯, 提示無法 構成重寫.

【重寫和重載的區別】

即:方法重載是一個類的多態性表現,而方法重寫是子類與父類的一種多態性表現。

?

【重寫的設計原則】

對于已經投入使用的類,盡量不要進行修改。最好的方式是:重新定義一個新的類,來重復利用其中共性的內容, 并且添加或者改動新的內容。 例如:若干年前的手機,只能打電話,發短信,來電顯示只能顯示號碼,而今天的手機在來電顯示的時候,不僅僅 可以顯示號碼,還可以顯示頭像,地區等。在這個過程當中,我們不應該在原來老的類上進行修改,因為原來的 類,可能還在有用戶使用,正確做法是:新建一個新手機的類,對來電顯示這個方法重寫就好了,這樣就達到了我 們當今的需求了。

?

靜態綁定:也稱為前期綁定(早綁定),即在編譯時,根據用戶所傳遞實參類型就確定了具體調用那個方法。典型代 表函數重載。

動態綁定:也稱為后期綁定(晚綁定),即在編譯時,不能確定方法的行為,需要等到程序運行時,才能夠確定具體 調用那個類的方法。

向上轉移和向下轉型

向上轉型

向上轉型:實際就是創建一個子類對象,將其當成父類對象來使用。

語法格式:父類類型 對象名 = new 子類類型()

Animal animal = new Cat("元寶",2);

animal是父類類型,但可以引用一個子類對象,因為是從小范圍向大范圍的轉換。

【使用場景】

1. 直接賦值

2. 方法傳參

3. 方法返回

public 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) {Animal cat = new Cat("元寶",2); // 1. 直接賦值:子類對象賦值給父類對象 Dog dog = new Dog("小七", 1);eatFood(cat); eatFood(dog);Animal animal = buyAnimal("狗"); animal.eat();animal = buyAnimal("貓"); animal.eat();}}

向上轉型的優點:讓代碼實現更簡單靈活。

向上轉型的缺陷:不能調用到子類特有的方法。

向下轉型

將一個子類對象經過向上轉型之后當成父類方法使用,再無法調用子類的方法,但有時候可能需要調用子類特有的 方法,此時:將父類引用再還原為子類對象即可,即向下轉換。

public class TestAnimal {public static void main(String[] args) { Cat cat = new Cat("元寶",2); Dog dog = new Dog("小七", 1);// 向上轉型 Animal animal = cat; animal.eat(); animal = dog; animal.eat();// 編譯失敗,編譯時編譯器將animal當成Animal對象處理 // 而Animal類中沒有bark方法,因此編譯失敗 // animal.bark();// 向上轉型 // 程序可以通過編程,但運行時拋出異常---因為:animal實際指向的是狗 // 現在要強制還原為貓,無法正常還原,運行時拋出:ClassCastException cat = (Cat)animal; cat.mew();// animal本來指向的就是狗,因此將animal還原為狗也是安全的 dog = (Dog)animal; dog.bark();}}

向下轉型用的比較少,而且不安全,萬一轉換失敗,運行時就會拋異常。Java中為了提高向下轉型的安全性,引入了instanceof,如果該表達式為true,則可以安全轉換。

public class TestAnimal {public static void main(String[] args) { Cat cat = new Cat("元寶",2); Dog dog = new Dog("小七", 1);// 向上轉型 Animal animal = cat; animal.eat(); animal = dog; animal.eat();if(animal instanceof Cat){ cat = (Cat)animal; cat.mew(); }if(animal instanceof Dog){ dog = (Dog)animal; dog.bark(); }}}

?instanceof關鍵詞官方介紹:https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.20.2

多態的優缺點

假設有如下代碼:

class Shape { //屬性....public void draw() { System.out.println("畫圖形!"); } } class Rect extends Shape{ @Override public void draw() { System.out.println("?"); } } class Cycle extends Shape{ @Override public void draw() { System.out.println("●"); }} class Flower extends Shape{ @Override public void draw() { System.out.println("?"); } }

【使用多態的好處】

1. 能夠降低代碼的 "圈復雜度", 避免使用大量的 if - else

什么叫 "圈復雜度" ?

圈復雜度是一種描述一段代碼復雜程度的方式. 一段代碼如果平鋪直敘, 那么就比較簡單容易理解. 而如 果有很多的條件分支或者循環語句, 就認為理解起來更復雜.

因此我們可以簡單粗暴的計算一段代碼中條件語句和循環語句出現的個數, 這個個數就稱為 "圈復雜度". 如果一個方法的圈復雜度太高, 就需要考慮重構.

不同公司對于代碼的圈復雜度的規范不一樣. 一般不會超過 10 .

例如我們現在需要打印的不是一個形狀了, 而是多個形狀. 如果不基于多態, 實現代碼如下:

public static void drawShapes() {Rect rect = new Rect();Cycle cycle = new Cycle();Flower ?ower = new Flower();String[] shapes = {"cycle", "rect", "cycle", "rect", "?ower"};for (String shape : shapes) {if (shape.equals("cycle")) {cycle.draw();} else if (shape.equals("rect")) {rect.draw();} else if (shape.equals("?ower")) {?ower.draw();}}}

如果使用使用多態, 則不必寫這么多的 if - else 分支語句, 代碼更簡單.

public static void drawShapes() {// 我們創建了一個 Shape 對象的數組.Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),new Rect(), new Flower()};for (Shape shape : shapes) {shape.draw(); }}

2. 可擴展能力更強

如果要新增一種新的形狀, 使用多態的方式代碼改動成本也比較低.

class Triangle extends Shape {@Overridepublic void draw() {System.out.println("△");}}

對于類的調用者來說(drawShapes方法), 只要創建一個新類的實例就可以了, 改動成本很低.

而對于不用多態的情況, 就要把 drawShapes 中的 if - else 進行一定的修改, 改動成本更高.

多態缺陷:代碼的運行效率降低。

1. 屬性沒有多態性

當父類和子類都有同名屬性的時候,通過父類引用,只能引用父類自己的成員屬性

2. 構造方法沒有多態性

見如下代碼~

避免在構造方法中調用重寫的方法

一段有坑的代碼. 我們創建兩個類, B 是父類, D 是子類. D 中重寫 func 方法. 并且在 B 的構造方法中調用 func

class B {public B() { // do nothing func(); }public void func() { System.out.println("B.func()"); }}class D extends B {private int num = 1; @Override public void func() { System.out.println("D.func() " + num); }}public class Test { public static void main(String[] args) {D d = new D();}}// 執行結果 
D.func() 0

構造 D 對象的同時, 會調用 B 的構造方法.

B 的構造方法中調用了 func 方法, 此時會觸發動態綁定, 會調用到 D 中的 func

此時 D 對象自身還沒有構造, 此時 num 處在未初始化的狀態, 值為 0. 如果具備多態性,num的值應該是1.

所以在構造函數內,盡量避免使用實例方法,除了?nal和private方法。

結論: "用盡量簡單的方式使對象進入可工作狀態", 盡量不要在構造器中調用方法(如果這個方法被子類重寫, 就會觸 發動態綁定, 但是此時子類對象還沒構造完成), 可能會出現一些隱藏的但是又極難發現的問題.

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/717673.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/717673.shtml
英文地址,請注明出處:http://en.pswp.cn/news/717673.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

循環隊列與循環雙端隊列

文章目錄 前言循環隊列循環雙端隊列 前言 1、學習循環隊列和循環雙端隊列能加深我們對隊列的理解,提高我們的編程能力。 2、本文循環隊列使用的是數組,循環雙端隊列用的是雙向鏈表 3、題目連接:設計循環隊列 ,設計循環雙端隊列。 …

【機器學習】有監督學習算法之:支持向量機

支持向量機 1、引言2、決策樹2.1 定義2.2 原理2.3 實現方式2.4 算法公式2.5 代碼示例 3、總結 1、引言 小屌絲:魚哥,泡澡啊。 小魚:不去 小屌絲:… 此話當真? 小魚:此話不假 小屌絲:到底去還是…

Linux 網絡接口的混雜模式(Promiscuous mode)認知

寫在前面 博文內容為 混雜模式的簡單認知理解不足小伙伴幫忙指正 認定一件事,即使拿十分力氣都無法完成,也要拿出十二分力氣去努力。 —《劍來》 網絡接口的混雜模式 混雜模式(Promiscuous mode),簡稱 Promisc mode,俗稱監聽模式…

什么是支持向量機(Support vector machine)和其原理

作為機器學習的基礎算法,SVM被反復提及,西瓜書、wiki都能查到詳細介紹,但是總是覺得還差那么點,于是決定自己總結一下。 一、什么是SVM? 1、解決什么問題? SVM,最原始的版本是用于最簡單的線…

藍橋杯備賽第五篇(動態規劃)

1.數位dp public class Main {static long[] limit;static int length;static long[][] dp;public static long dfs(int pos, int pre, boolean flag, boolean lead) {if (pos length) return 1;if (!flag && !lead && dp[pos][pre] ! -1) return dp[pos][pr…

總結 HashTable, HashMap, ConcurrentHashMap 之間的區別

1.多線程環境使用哈希表 HashMap 不行,線程不安全 更靠譜的,Hashtable,在關鍵方法上加了synchronized 后來標準庫又引入了一個更好的解決方案;ConcurrentHashMap 2.HashMap 首先HashMap本身線程不安全其次HashMap的key值可以為空(當key為空時,哈希會…

【Java數據結構】——五道算法題讓你靈活運用Map和Set

目錄 一.只出現一次的數字 二.寶石與石頭 三.舊鍵盤 四.給定一個數組,統計每個元素出現的次數 五.前K個高頻單詞 一.只出現一次的數字 136. 只出現一次的數字 - 力扣(LeetCode) 算法原理:我們將nums中每個元素都存入到set中…

C/C++嵌入式開發環境搭建,Qt交叉編譯,cmake交叉編譯,clion/vscode遠程開發

目錄 交叉編譯簡介cmake 交叉編譯clion 交叉編譯vscode 遠程嵌入式開發Qt交叉編譯1.安裝交叉編譯工具2.交叉編譯qt庫3.將交叉編譯的Qt庫復制到板子上4.安裝和配置 Qt Creator,支持交叉編譯5.QT嵌入式開發6.QT嵌入式開發報錯解決QIconvCodec::convertToUnicode: usin…

ASUS華碩天選5筆記本電腦FX607JV原裝出廠Win11系統下載

ASUS TUF Gaming F16 FX607JV天選五原廠Windows11系統 適用型號: FX607JU、FX607JI、FX607JV、 FX607JIR、FX607JVR、FX607JUR 下載鏈接:https://pan.baidu.com/s/1l963wqxT0q1Idr98ACzynQ?pwd0d46 提取碼:0d46 原廠系統自帶所有驅動、…

TypeScript中 “ <> “ 語法 和 “ : “ 怎么使用

在 TypeScript 中&#xff0c;尖括號語法(<Type>)和as關鍵字(value as Type)都是用于類型斷言&#xff0c;而冒號(:)用于類型注解。這三種語法在不同的場景下使用&#xff1a; 尖括號語法和as關鍵字&#xff1a; 尖括號語法(<Type>value)&#xff1a; 這種語法在…

[LeetBook]【學習日記】鏈表反轉

來源于「Krahets」的《圖解算法數據結構》 https://leetcode.cn/leetbook/detail/illustration-of-algorithm/ 鏈表反轉的遞歸要點 遞歸終止條件為當前節點為空&#xff0c;表明遍歷到了鏈表尾部遞歸函數傳入參數為當前節點的下一個節點按照是否重新開辟存儲空間分類下面只寫…

python自動化學習--3.8python操作EXCEL文件python日志收集處理

1、Excel文件處理 安裝 openpxl 第三方庫 openpxl 模塊三大組件: 1、工作簿 &#xff08;包含多個sheet工作表&#xff09; 2、工作表 &#xff08;某個數據包含在某個工作表&#xff09; 3、單元格 1、創建excel工作簿 import openpyxl"""Excel表格的創建…

【簡說八股】Spring事務失效可能是哪些原因?

Spring事務介紹 Spring事務是指在Spring框架中對數據庫操作進行管理的一種機制&#xff0c;它確保一組數據庫操作要么完全執行成功&#xff08;提交&#xff09;&#xff0c;要么完全不執行&#xff08;回滾&#xff09;&#xff0c;從而保持數據一致性和完整性。 Spring框架…

GotoXy控制臺光標的位置更新

光標控制解釋 控制臺的光標更新方法, 用于控制數據輸出位置 void gotoXY(int x, int y)//新函數&#xff1a;更新光標 {COORD c;c.X x;c.Y y;SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), c); }代碼解釋 這段代碼定義了一個名為 gotoXY 的函數&#xff0c;…

設計模式-裝飾者模式應用實踐

裝飾者模式&#xff08;Decorator Pattern&#xff09;是一種結構型設計模式&#xff0c;它允許動態地向一個現有的對象添加新的功能&#xff0c;同時不改變其結構。這種模式通過創建一個裝飾類來包裝原有的類&#xff0c;提供額外的行為。 下面是一個使用 Java 實現裝飾者模式…

【Spring Boot】實現全局異常處理

1.定義基礎異常接口類 /*** description: 服務接口類* author: MrVK* date: 2021/4/19 21:39*/ public interface BaseErrorInfoInterface {/*** 錯誤碼* return*/String getResultCode();/*** 錯誤描述* return*/String getResultMsg(); } 2.定義錯誤處理枚舉類 /*** desc…

小伙伴詢問AI該怎么學習?本人的一點總結,以思維導圖呈現

如有需要思維導圖的在后臺請留郵箱&#xff0c;相關知識結構目錄 部分導圖

nn.Linear() 使用提醒

原本以為它是和nn.Conv2d()一樣&#xff0c;就看第二個維度的數值&#xff0c;今天才知道&#xff0c;它是只看最后一個維度的數值&#xff01;&#xff01;&#xff01; 例子1 Descripttion: Result: Author: Philo Date: 2024-02-27 14:33:50 LastEditors: Philo LastEditT…

git使用merge命令把dev分支的mian.js文件和src下面的vuex文件夾以及config文件夾單獨合并到master分支上

使用 git merge 命令來單獨合并特定文件或文件夾到另一個分支通常不是最直接的方法&#xff0c;因為 merge 命令是用來合并兩個分支的所有更改的。然而&#xff0c;你可以通過 git cherry-pick 命令或者通過創建臨時補丁&#xff08;patch&#xff09;來實現這一點。 下面是一個…

秒殺的時候怎么使用Redis?

商品信息存儲&#xff1a;在Redis中存儲秒殺商品的庫存信息。可以使用Redis的Hash數據類型&#xff0c;將商品ID作為字段&#xff0c;庫存數量作為值存儲在Hash中。例如&#xff0c;HSET seckill_goods stock_1 100表示商品ID為stock_1的商品庫存數量為100。 秒殺訂單存儲&…