物理機(jī)安裝虛擬機(jī)做網(wǎng)站想建立自己的網(wǎng)站
本篇會(huì)加入個(gè)人的所謂‘魚(yú)式瘋言’
??????魚(yú)式瘋言:??????此瘋言非彼瘋言
而是理解過(guò)并總結(jié)出來(lái)通俗易懂的大白話,
小編會(huì)盡可能的在每個(gè)概念后插入魚(yú)式瘋言,幫助大家理解的.
🤭🤭🤭可能說(shuō)的不是那么嚴(yán)謹(jǐn).但小編初心是能讓更多人能接受我們這個(gè)概念 !!!
前言
在前面兩篇文章中,小編帶著友友們?cè)敿?xì)的熟悉了我們面向?qū)ο蟮那皟墒?------- 封裝, 繼承
而在本篇文章中小編講帶著小伙伴具體走進(jìn)咱們第三式: 多態(tài)
竟然都進(jìn)入第三式了, 我們的難度也會(huì)加大哦,小伙伴們一定要認(rèn)真的閱讀小編的講解哦 💖 💖 💖
還是老規(guī)矩,小伙伴們從目錄開(kāi)始吧 💖 💖 💖
目錄
- 重寫
- 向上轉(zhuǎn)型與向下轉(zhuǎn)型
- 多態(tài)的實(shí)現(xiàn)
- 多態(tài)的優(yōu)缺點(diǎn)
當(dāng)小編寫出第一個(gè)小標(biāo)題的時(shí)候,小愛(ài)同學(xué)就問(wèn)了,
我們不是講多態(tài)嗎? 怎么變成重寫了,那重寫是什么? 向下轉(zhuǎn)型和向上轉(zhuǎn)型又什么呢?
淺淺和小伙伴先透露一下哦,我們要實(shí)現(xiàn)多態(tài)就需要兩個(gè)原理:
一個(gè)是 重寫
另外一個(gè)就是我們的 向上轉(zhuǎn)型 。
所以讓小編帶著帶著循序漸進(jìn),腳踏實(shí)地的走入多態(tài)的海洋哦。 💖 💖 💖
一. 重寫
1. 重寫的初識(shí)
重寫(override):也稱為覆蓋。
重寫是子類對(duì)父類非靜態(tài)、非private修飾,非final修飾,非構(gòu)造方法等的實(shí)現(xiàn)過(guò)程進(jìn)行重新編寫, 返回值和形參都不能改變。
重寫的好處在于 子類可以根據(jù)需要,定義特定于自己的行為。
也就是說(shuō)子類能夠根據(jù)需要實(shí)現(xiàn)父類的方法。
魚(yú)式瘋言
如果非要小編用9個(gè)字來(lái)概括的話,非他們莫屬了 😁 😁 😁
即外殼不變,核心重寫!
2. 舉個(gè)栗子
class Dog {public void bark() {System.out.println("woof");}
}class Hound extends Dog {public void sniff() {System.out.println("sniff");}@Overridepublic void bark() {System.out.println("bowl");}
}class Test {public static void main(String[] args) {Hound hound=new Hound();hound.bark();}
}
通過(guò)上面的栗子證明了我們方法重寫的哪些規(guī)則呢
方法重寫規(guī)則
-
子類在重寫父類的方法時(shí),一般必須與父類方法原型一致: 返回值類型 方法名 (參數(shù)列表) 要完全一致
被重寫的方法返回值類型可以不同,但是必須是具有父子關(guān)系的 -
訪問(wèn)權(quán)限不能比父類中被重寫的方法的 訪問(wèn)權(quán)限更低 。
-
例如:如果父類方法被 public修飾 ,則子類中重寫該方法就不能聲明為 protected
-
父類被static、private修飾的方法、構(gòu)造方法都不能被重寫。
-
重寫的方法, 可以使用 @Override 注解來(lái)顯式指定.
-
有了這個(gè)注解能幫我們進(jìn)行一些 合法性校驗(yàn). 例如不小心將方法名字拼寫錯(cuò)了 (比如寫成 aet),
-
那么此時(shí)編譯器就會(huì)發(fā)現(xiàn)父類中沒(méi)有 aet 方法, 就會(huì)編譯報(bào)錯(cuò), 提示無(wú)法構(gòu)成重寫.
魚(yú)式瘋言
給大家舉個(gè)生活中的栗子就明白我們 重寫的重要性 了
對(duì)于已經(jīng)投入使用的類,盡量不要進(jìn)行修改。
最好的方式是:重新定義一個(gè)新的類,來(lái)重復(fù)利用其中共性的內(nèi)容,并且添加或者改動(dòng)新的內(nèi)容。
例如:若干年前的手機(jī),只能打電話,發(fā)短信,來(lái)電顯示只能顯示號(hào)碼,而今天的手機(jī)在來(lái)電顯示的時(shí)候,不僅僅可以顯示號(hào)碼,還可以顯示頭像,地區(qū)等。
在這個(gè)過(guò)程當(dāng)中,我們不應(yīng)該在原來(lái)老的類上進(jìn)行修改,因?yàn)樵瓉?lái)的類,可能還在有用戶使用,正確做法是:新建一個(gè)新手機(jī)的類,對(duì)來(lái)電顯示這個(gè)方法重寫就好了,這樣就達(dá)到了我們當(dāng)今的需求了。
當(dāng)我們熟悉了重寫之后,和我們之前學(xué)過(guò)的重載又有什么異同之處呢 💕 💕 💕
3. 重寫與重載的區(qū)別
class Dog {public void bark() {System.out.println("woof");}public void bark(int num) {for (int i = 0; i < num; i++) {System.out.print("woof ");}}
}class Hound extends Dog {public void sniff() {System.out.println("sniff");}@Overridepublic void bark() {System.out.println("bowl");}
}class Test {public static void main(String[] args) {Hound hound=new Hound();hound.bark();Dog dog=new Dog();dog.bark(5);}
}
從中我們把他們分解著來(lái)看
從什么的圖解中我們就明白了,重寫和重載的最本質(zhì)的區(qū)別
即: 方法重載是一個(gè)類的多態(tài)性表現(xiàn),而方法重寫是子類與父類的一種多態(tài)性表現(xiàn)。
魚(yú)式瘋言
有圖有真相
、
二. 向上轉(zhuǎn)型和向下轉(zhuǎn)型
1. 向上轉(zhuǎn)型
<1>. 向上轉(zhuǎn)型簡(jiǎn)介
向上轉(zhuǎn)型:實(shí)際就是創(chuàng)建一個(gè)子類對(duì)象,將其當(dāng)成父類對(duì)象來(lái)使用。
語(yǔ)法格式::父類類型 對(duì)象名 = new 子類類型()
et:Animal animal = new Cat(“元寶”,2);
animal是父類類型,但可以引用一個(gè)子類對(duì)象,因?yàn)槭菑男》秶虼蠓秶霓D(zhuǎn)換。
<2>. 舉個(gè)栗子
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+"吃飯");}}class Cat extends Animal {public Cat(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(super.name+"吃魚(yú)~~~~~");}
}class Dog extends Animal {public Dog(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(super.name+"吃骨頭~~~~");}
}class Test {public static void main(String[] args) {Animal animal1=new Cat("小喵",14);animal1.eat();Animal animal2=new Dog("小汪",18);animal2.eat();}
}
魚(yú)式瘋言:
畫(huà)個(gè)小圖說(shuō)明下:
<3>. 向上轉(zhuǎn)型的實(shí)際運(yùn)用
小編總結(jié)向上轉(zhuǎn)型的三個(gè)使用場(chǎng)景
- 直接賦值
- 方法傳參
- 方法返回
下面讓小伙們一起來(lái)證明下這三個(gè)使用場(chǎng)景吧
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+"吃飯");}}class Cat extends Animal {public Cat(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(super.name+"吃魚(yú)~~~~~");}
}class Dog extends Animal {public Dog(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(super.name+"吃骨頭~~~~");}
}class TestAnimal {// 2. 方法傳參:形參為父類型引用,可以接收任意子類的對(duì)象public static void eatFood(Animal a){a.eat();}// 3. 作返回值:返回任意子類對(duì)象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. 直接賦值:子類對(duì)象賦值給父類對(duì)象Animal cat = new Cat("元寶",2);Dog dog = new Dog("小七", 1);eatFood(cat);eatFood(dog);Animal animal = buyAnimal("狗");animal.eat();animal = buyAnimal("貓");animal.eat();}
}
魚(yú)式瘋言
最后小編補(bǔ)充個(gè)點(diǎn)哦
向上轉(zhuǎn)型的 優(yōu)點(diǎn) :讓代碼實(shí)現(xiàn)更簡(jiǎn)單靈活。
向上轉(zhuǎn)型的 缺陷 :不能調(diào)用到子類特有的方法。
2. 向下轉(zhuǎn)型
有 向上轉(zhuǎn)型 就必然有 向下轉(zhuǎn)型
<1>. 向下轉(zhuǎn)型的簡(jiǎn)介
將一個(gè)子類對(duì)象經(jīng)過(guò)向上轉(zhuǎn)型之后當(dāng)成父類方法使用,再無(wú)法調(diào)用子類的方法。
但有時(shí)候可能需要調(diào)用 子類特有的方法 。
此時(shí):將父類引用再還原為子類對(duì)象即可,即向下轉(zhuǎn)換。
這是下面這張圖啦 😁 😁 😁
<2>. 舉個(gè)栗子
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+"吃飯");}}class Cat extends Animal {public Cat(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(super.name+"吃魚(yú)~~~~~");}
}class Dog extends Animal {public Dog(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(super.name+"吃骨頭~~~~");}
}class TestAnimal {public static void main(String[] args) {Cat cat = new Cat("元寶",2);Dog dog = new Dog("小七", 1);
// 向上轉(zhuǎn)型Animal animal = cat;animal.eat();animal = dog;animal.eat();// 編譯失敗,編譯時(shí)編譯器將animal當(dāng)成Animal對(duì)象處理// 而Animal類中沒(méi)有bark方法,因此編譯失敗// animal.bark();// 向上轉(zhuǎn)型// 程序可以通過(guò)編程,但運(yùn)行時(shí)拋出異常---因?yàn)?#xff1a;animal實(shí)際指向的是狗// 現(xiàn)在要強(qiáng)制還原為貓,無(wú)法正常還原,運(yùn)行時(shí)拋出:ClassCastExceptioncat = (Cat)animal;cat.eat();// animal本來(lái)指向的就是狗,因此將animal還原為狗也是安全的dog = (Dog)animal;dog.eat();}
}
上面的結(jié)果怎么會(huì)出現(xiàn)這樣子呢,原來(lái)啊
所以 向下轉(zhuǎn)型用的比較少,而且不安全,萬(wàn)一轉(zhuǎn)換失敗,運(yùn)行時(shí)就會(huì)拋異常。
Java中為了提高向下轉(zhuǎn)型的安全性
引入了instanceof ,如果該表達(dá)式為true,則可以安全轉(zhuǎn)換。
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+"吃飯");}}class Cat extends Animal {public Cat(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(super.name+"吃魚(yú)~~~~~");}public void mew() {System.out.println(super.name+"正在mew~~~~");}
}class Dog extends Animal {public Dog(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(super.name+"吃骨頭~~~~");}public void bark() {System.out.println(super.name+"正在bark~~~~");}
}class TestAnimal {public static void main(String[] args) {Cat cat = new Cat("元寶",2);Dog dog = new Dog("小七", 1);
// 向上轉(zhuǎn)型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();}}}
很明顯我們看到當(dāng)我們用 instanceof 關(guān)鍵字時(shí),代碼的安全性就提高了很多
三. 多態(tài)的實(shí)現(xiàn)
1.多態(tài)的簡(jiǎn)介
多態(tài)的概念:通俗來(lái)說(shuō),就是多種形態(tài)
具體點(diǎn)就是去完成某個(gè)行為,當(dāng)不同的對(duì)象去完成時(shí)會(huì)產(chǎn)生出不同 的狀態(tài)。
好比下面這些小圖 😎 😎 😎
魚(yú)式瘋言
總的來(lái)說(shuō):同一件事情,發(fā)生在 不同對(duì)象身上,就會(huì)產(chǎn)生不同的結(jié)果。
2. 多態(tài)實(shí)現(xiàn)的條件
在java中要實(shí)現(xiàn)多態(tài),必須要滿足如下幾個(gè)條件,缺一不可:
- 必須在 繼承體系 下
- 子類必須要對(duì)父類中方法進(jìn)行重寫
- 通過(guò)父類的引用 調(diào)用重寫的方法
<1>. 舉個(gè)栗子
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+"吃飯");}}class Cat extends Animal {public Cat(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(super.name+"吃魚(yú)~~~~~");}public void mew() {System.out.println(super.name+"正在mew~~~~");}
}class Dog extends Animal {public Dog(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(super.name+"吃骨頭~~~~");}public void bark() {System.out.println(super.name+"正在bark~~~~");}
}///分割線//class TestAnimal {// 編譯器在編譯代碼時(shí),并不知道要調(diào)用Dog 還是 Cat 中eat的方法// 等程序運(yùn)行起來(lái)后,形參a引用的具體對(duì)象確定后,才知道調(diào)用那個(gè)方法// 注意:此處的形參類型必須時(shí)父類類型才可以
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);
}}
在上述代碼中, 分割線上方的代碼是 類的實(shí)現(xiàn)者 編寫的, 分割線下方的代碼是 類的調(diào)用者 編寫的.
當(dāng)類的調(diào)用者在編寫 eat 這個(gè)方法的時(shí)候, 參數(shù)類型為 Animal (父類)
此時(shí)在該方法內(nèi)部并不知道, 也不關(guān)注當(dāng)前的a 引用指向的是哪個(gè)類型 (哪個(gè)子類)的實(shí)例.
此時(shí) a這個(gè)引用調(diào)用 eat方法可能會(huì)有多種不同的表現(xiàn)(和 a 引用的實(shí)例相關(guān)), 這種行為就稱為 多態(tài).
我們面向?qū)ο缶幊痰亩鄳B(tài)只是一種 編程思維
而真正操作是我們稱為: 動(dòng)態(tài)綁定
2. 靜態(tài)綁定與動(dòng)態(tài)綁定
<1>. 靜態(tài)綁定
靜態(tài)綁定:也稱為 前期綁定(早綁定)
class Test9 {public static int sum(int x,int y) {return x+y;}public static double sum (double x,double y,double z) {return x+y+z;}public static void main(String[] args) {System.out.println(sum(2, 6));System.out.println(sum(1.2, 3.5, 9.3));}
}
即在編譯時(shí),根據(jù)用戶所傳遞實(shí)參類型就確定了具體 調(diào)用 那個(gè)方法。
典型代表函數(shù)重載。
<2>. 動(dòng)態(tài)綁定
動(dòng)態(tài)綁定:也稱為 后期綁定(晚綁定)
具體的栗子我們?cè)?strong>多態(tài)中已經(jīng)充分的體現(xiàn)了,下編在這里就不贅述了 😊 😊 😊
即在編譯時(shí),不能確定 方法的行為 ,需要等到 程序運(yùn)行 時(shí),才能夠確定具體調(diào)用那個(gè) 類的方法 。
魚(yú)式瘋言
用大白話說(shuō)就是
動(dòng)態(tài)綁定: 編譯時(shí)確定,運(yùn)行時(shí)改變
靜態(tài)綁定: 編譯時(shí)確定,運(yùn)行時(shí)也確定
四. 多態(tài)的優(yōu)缺點(diǎn)
class Shape {//屬性....public void draw() {System.out.println("畫(huà)圖形!");}
}
class Rect extends Shape{@Overridepublic void draw() {System.out.println("?");}
}
class Cycle extends Shape{@Overridepublic void draw() {System.out.println("●");}
}
【使用多態(tài)的好處】
什么叫 “圈復(fù)雜度” ?
圈復(fù)雜度是一種描述一段代碼復(fù)雜程度的方式. 一段代碼如果平鋪直敘, 那么就比較簡(jiǎn)單容易理解.
而如果有很多的條件分支或者循環(huán)語(yǔ)句, 就認(rèn)為理解起來(lái)更復(fù)雜.
因此小伙伴們可以簡(jiǎn)單粗暴的計(jì)算一段代碼中條件語(yǔ)句和循環(huán)語(yǔ)句出現(xiàn)的個(gè)數(shù), 這個(gè)個(gè)數(shù)就稱為 “圈復(fù)雜度”.
如果一個(gè)方法的圈復(fù)雜度太高, 就需要考慮重構(gòu).
不同公司對(duì)于代碼的圈復(fù)雜度的規(guī)范不一樣. 一般不會(huì)超過(guò) 10 .
1. 圈復(fù)雜度低
<1>.普通栗子one
class Shape {//屬性....public void draw() {System.out.println("畫(huà)圖形!");}
}
class Rect extends Shape{@Overridepublic void draw() {System.out.println("?");}
}
class Cycle extends Shape{@Overridepublic void draw() {System.out.println("●");}
}class Flower extends Shape{@Overridepublic void draw() {System.out.println("?");}
}class Test {public static void drawShapes() {Rect rect = new Rect();Cycle cycle = new Cycle();Flower flower = new Flower();String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};for (String shape : shapes) {if (shape.equals("cycle")) {cycle.draw();} else if (shape.equals("rect")) {rect.draw();} else if (shape.equals("flower")) {flower.draw();}}}public static void main(String[] args) {drawShapes();}
}
從這個(gè)普通栗子中是不是就能看出我們的 圈復(fù)雜度 是不是 很高
那如果是使用多態(tài)的思想來(lái)解決問(wèn)題呢 🤔 🤔 🤔
<2>.多態(tài)栗子two
class Shape {//屬性....public void draw() {System.out.println("畫(huà)圖形!");}
}
class Rect extends Shape{@Overridepublic void draw() {System.out.println("?");}
}
class Cycle extends Shape{@Overridepublic void draw() {System.out.println("●");}
}class Flower extends Shape{@Overridepublic void draw() {System.out.println("?");}
}class Test {public static void drawShapes() {
// 我們創(chuàng)建了一個(gè) Shape 對(duì)象的數(shù)組.Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),new Rect(), new Flower()};for (Shape shape : shapes) {shape.draw();}}public static void main(String[] args) {drawShapes();}
}
2. 可擴(kuò)展能力強(qiáng)
如果要新增一種新的形狀, 使用多態(tài)的方式代碼改動(dòng)成本也比較低.
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("△");
}
}
對(duì)于類的調(diào)用者來(lái)說(shuō)**(drawShapes方法),** 只要?jiǎng)?chuàng)建一個(gè)新類的實(shí)例就可以了, 改動(dòng)成本很低.
而對(duì)于不用多態(tài)的情況, 就要把 drawShapes 中的 if - else 進(jìn)行一定的修改, 改動(dòng)成本更高.
有優(yōu)點(diǎn)就會(huì)有缺點(diǎn)
3. 多態(tài)的缺陷
- 屬性沒(méi)有多態(tài)性
當(dāng)父類和子類都有同名屬性的時(shí)候,通過(guò)父類引用,只能引用父類自己的成員屬性
- 構(gòu)造方法沒(méi)有多態(tài)性
見(jiàn)如下代碼
4. 避免在構(gòu)造方法中調(diào)用重寫方法
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);}
}class Test {public static void main(String[] args) {D d = new D();}
}
- 構(gòu)造 D 對(duì)象的同時(shí), 會(huì)調(diào)用 B 的構(gòu)造方法.
- B 的構(gòu)造方法中調(diào)用了 func 方法, 此時(shí)會(huì)觸發(fā)動(dòng)態(tài)綁定, 會(huì)調(diào)用到 D 中的 func
- 此時(shí) D 對(duì)象自身還沒(méi)有構(gòu)造, 此時(shí) num 處在未初始化的狀態(tài), 值為 0. 如果具備多態(tài)性,num的值應(yīng)該是1.
- 所以在構(gòu)造函數(shù)內(nèi),盡量避免使用實(shí)例方法,除了final和private方法。
魚(yú)式瘋言
結(jié)論:
“用盡量簡(jiǎn)單的方式使對(duì)象進(jìn)入可工作狀態(tài)”, 盡量不要在
構(gòu)造器中調(diào)用方法
(如果這個(gè)方法被子類重寫, 就會(huì)觸發(fā)動(dòng)態(tài)綁定, 但是此時(shí)子類對(duì)象還沒(méi)構(gòu)造完成)
可能會(huì)出現(xiàn)一些隱藏的但是又極難發(fā)現(xiàn)的問(wèn)題.
總結(jié)
- 重寫: 理解了重寫是在什么前提下進(jìn)行,并與重載進(jìn)行對(duì)比
- 向上轉(zhuǎn)型與向下轉(zhuǎn)型: 明白了向上整型的初始化以及不同的使用場(chǎng)景,向下轉(zhuǎn)型的 instanceof 檢驗(yàn)運(yùn)用
- 多態(tài)的實(shí)現(xiàn): 理解多態(tài)的概念,以及多態(tài)的實(shí)際運(yùn)用和使用特點(diǎn)
- 多態(tài)的優(yōu)缺點(diǎn):多態(tài)的優(yōu)點(diǎn)的多個(gè)典型栗子說(shuō)明,以及多態(tài)的缺點(diǎn)的不規(guī)范使用的詳解
如果覺(jué)得小編寫的還不錯(cuò)的咱可支持 三連 下 (定有回訪哦) , 不妥當(dāng)?shù)脑壅?qǐng)?jiān)u論區(qū) 指正
希望我的文章能給各位寶子們帶來(lái)哪怕一點(diǎn)點(diǎn)的收獲就是 小編創(chuàng)作 的最大 動(dòng)力 💖 💖 💖