自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

不是吧?不會多態(tài),你還說自己會Java

開發(fā) 后端
”今天是周五,跟往常一樣踩點來到了公司。坐到自己的工位上打開電腦,"又是搬磚的一天"。想歸想,還是"熟練"的打開了 Idea,看了下今天的需求,便敲起了代碼。

 [[341866]]

本文轉(zhuǎn)載自微信公眾號「小菜良記  」,作者蔡不菜丶 。轉(zhuǎn)載本文請聯(lián)系小菜良記  公眾號。   

”今天是周五,跟往常一樣踩點來到了公司。坐到自己的工位上打開電腦,"又是搬磚的一天"。想歸想,還是"熟練"的打開了 Idea,看了下今天的需求,便敲起了代碼。咦,這些代碼是誰寫的,怎么出現(xiàn)在我的代碼里面,而且還是待提交狀態(tài),我記得我沒寫過呀,饒有興趣的看了看:

 

這不是多態(tài)嗎,誰在我電腦寫的測試,不禁一陣奇怪。

"你看看這會輸出什么結(jié)果?"一陣聲音從身后傳來,因為在思考輸出結(jié)果,也沒在意聲音的來源,繼續(xù)看了看代碼,便得出結(jié)論:

  1. /* 
  2.     polygon() before cal() 
  3.     square.cal(), border = 2 
  4.     polygon() after cal() 
  5.     square.square(), border = 4 
  6. */ 

心里想:就這?起碼也是名 Java 開發(fā)工程師好嗎,雖然平時搬搬磚,一些基本功還是有的。不禁有點得意了~

"這就是你的答案嗎?看來你也不咋的"聲音又突然響起,這次我不淡定了,尼瑪!這答案我也是在心里想的好嗎,誰能看得到啊,而且說得話讓人那么想施展一套阿威十八式。"你是誰啊?"帶著絲微疑惑和憤怒轉(zhuǎn)過了頭。怎么沒人?容不得我疑惑半分,"小菜,醒醒,你怎么上班時間就睡著了"上班時間,睡著了?我睜開了眼,看了下周圍環(huán)境,原來是夢啊,舒了一口氣。望眼就看到部門主管站在我面前,上班時間睡覺,你是身體不舒服還是咋樣?昨天寫了一堆 bug 沒改,今天又提交什么亂七八糟的東西上去,我看你這個月的績效是不想要的,而且基于你的表現(xiàn),我也要開始為部門考慮考慮了。

"我不是,我沒有,我也不知道怎么就睡著了,你聽我解釋啊!" 這句話還沒來得及說出口,心里的花我要帶你回家,在那深夜酒吧哪管它是真是假,請你盡情搖擺忘記鐘意的他,你是最迷人噶,你知道嗎,鬧鈴響了起來,我一下子立起身子,后背微濕,額頂微汗,看了下手機,周六,8點30分,原來那是夢啊!

奇怪,怎么會做那么奇怪的夢,也太嚇人了。然后就想到了夢中的那部分代碼,難道我的結(jié)果是錯的嗎?憑著記憶,在電腦上重新敲了出來,運行結(jié)果如下:

  1. /* 
  2.     polygon() before cal() 
  3.     square.cal(), border = 0 
  4.     polygon() after cal() 
  5.     square.square(), border = 4 
  6. */ 

square.cal(), border的結(jié)果居然是 0,而不是2。難道我現(xiàn)在連多態(tài)都不會了嗎?電腦手機前的你,不知道是否得出了正確答案了呢!不管有沒有,接下來就跟小菜一起來復(fù)習(xí)一下多態(tài)吧!

有些小伙伴疑惑的點可能不止square.cal(), border的結(jié)果是 0,也有為什么不是 square.square(), border = 4 先輸出的疑惑。那么我們就帶著疑惑,整起!

多態(tài)

「在面向?qū)ο蟮某绦蛟O(shè)計語言中,多態(tài)是繼數(shù)據(jù)抽象和繼承之后的第三種基本特征。」

多態(tài)不但能夠改善代碼的組織結(jié)構(gòu)和可讀性,還能夠創(chuàng)建可擴展的程序。多態(tài)的作用就是消除類型之間的耦合關(guān)系。

1. 向上轉(zhuǎn)型

根據(jù)里氏代換原則:任何基類可以出現(xiàn)的地方,子類一定可以出現(xiàn)。

對象既可以作為它自己本身的類型使用,也可以作為它的基類型使用。而這種把對某個對象的引用視為對其基類型的引用的做法被稱作為 - 向上轉(zhuǎn)型。因為父類在子類的上方,子類要引用父類,因此稱為 向上轉(zhuǎn)型。

  1. public class Animal { 
  2.     void eat() { 
  3.         System.out.println("Animal eat()"); 
  4.     } 
  5.  
  6. class Monkey extends Animal { 
  7.  
  8.     void eat() { 
  9.         System.out.println(" Monkey eat()"); 
  10.     } 
  11.  
  12. class test { 
  13.  
  14.     public static void start(Animal animal) { 
  15.         animal.eat(); 
  16.     } 
  17.  
  18.     public static void main(String[] args) { 
  19.         Monkey monkey = new Monkey(); 
  20.         start(monkey); 
  21.     } 
  22.  
  23. /* OUTPUT
  24. Monkey eat() 
  25. */ 

上述 test 類中的 start() 方法接收一個 Animal 的引用,自然也可以接收從Animal 的導(dǎo)出類。調(diào)用eat() 方法的時候,自然而然的使用到 Monkey 中定義的eat()方法,而不需要做任何的類型轉(zhuǎn)換。因為從 Monkey 向上轉(zhuǎn)型到 Animal 只能減少接口,而不會比Animal 的接口更少。

打個不是特別恰當(dāng)?shù)谋确剑耗愀赣H的財產(chǎn)會繼承給你,而你的財產(chǎn)還是你的,總的來說,你的財產(chǎn)不會比你父親的少。

 

忘記對象類型

在 test.start()方法中,定義傳入的是 Animal 的引用,但是卻傳入Monkey,這看起來似乎忘記了Monkey 的對象類型,那么為什么不直接把test類中的方法定義為void start(Monkey monkey),這樣看上去難道不會更直觀嗎。

直觀也許是它的優(yōu)點,但是就會帶來其他問題:Animal不止只有一個Monkey的導(dǎo)出類,這個時候來了個pig ,那么是不是就要再定義個方法為void start(Monkey monkey),重載用得挺溜嘛小伙子,但是未免太麻煩了。懶惰才是開發(fā)人員的天性。

因此這樣就有了多態(tài)的產(chǎn)生

2.顯露優(yōu)勢

「方法調(diào)用」中分為 靜態(tài)綁定和動態(tài)綁定。何為綁定:將一個方法調(diào)用同一個方法主體關(guān)聯(lián)起來被稱作綁定。

  • 靜態(tài)綁定:又稱為「前期綁定」。是在程序執(zhí)行前進行把綁定。我們平時聽到"靜態(tài)"的時候,不難免想到static關(guān)鍵字,被static關(guān)鍵字修飾后的變量成為靜態(tài)變量,這種變量就是在程序執(zhí)行前初始化的。前期綁定是面向過程語言中默認(rèn)的綁定方式,例如 C 語言只有一種方法調(diào)用,那就是前期綁定。

「引出思考:」

  1. public static void start(Animal animal) { 
  2.     animal.eat(); 

在start()方法中傳入的是Animal 的對象引用,如果有多個Animal的導(dǎo)出類,那么執(zhí)行eat()方法的時候如何知道調(diào)用哪個方法。如果通過前期綁定那么是無法實現(xiàn)的。因此就有了后期綁定。

動態(tài)綁定:又稱為后期綁定。是在程序運行時根據(jù)對象類型進行綁定的,因此又可以稱為運行時綁定。而 Java 就是根據(jù)它自己的后期綁定機制,以便在運行時能夠判斷對象的類型,從而調(diào)用正確的方法。

「小結(jié):」

Java 中除了 static 和 final 修飾的方法之外,都是屬于后期綁定

合理即正確

顯然通過動態(tài)綁定來實現(xiàn)多態(tài)是合理的。這樣子我們在開發(fā)接口的時候只需要傳入 基類 的引用,從而這些代碼對所有 基類 的 導(dǎo)出類 都可以正確的運行。

 

其中Monkey、Pig、Dog皆是Animal的導(dǎo)出類

Animal animal = new Monkey() 看上去不正確的賦值,但是上通過繼承,Monkey就是一種Animal,如果我們調(diào)用animal.eat()方法,不了解多態(tài)的小伙伴常常會誤以為調(diào)用的是Animal的eat()方法,但是最終卻是調(diào)用了Monkey自己的eat()方法。

Animal作為基類,它的作用就是為導(dǎo)出類建立公用接口。所有從Animal繼承出去的導(dǎo)出類都可以有自己獨特的實現(xiàn)行為。

可擴展性

有了多態(tài)機制,我們可以根據(jù)自己的需求對系統(tǒng)添加任意多的新類型,而不需要重載void start(Animal animal)方法。

在一個設(shè)計良好的OOP程序中,大多數(shù)或者所有方法都會遵循start()方法的模型,只與基類接口同行,這樣的程序就是具有「可擴展性」的,我們可以通過從通用的基類繼承出新的數(shù)據(jù)類型,從而添加一些功能,那些操縱基類接口的方法就不需要任何改動就可以應(yīng)用于新類。

失靈了?我們先來復(fù)習(xí)一下權(quán)限修飾符:

作用域 當(dāng)前類 用一個package 子孫類 其他package
public
protected ×
default × ×
private × × ×

「私有方法帶來的失靈」:

復(fù)習(xí)完我們再來看一組代碼:

  1. public class PrivateScope { 
  2.  
  3.     private void f() { 
  4.         System.out.println("PrivateScope f()"); 
  5.     } 
  6.  
  7.     public static void main(String[] args) { 
  8.         PrivateScope p = new PrivateOverride(); 
  9.         p.f(); 
  10.     } 
  11.  
  12. class PrivateOverride extends PrivateScope { 
  13.  
  14.     private void f() { 
  15.         System.out.println("PrivateOverride f()"); 
  16.     } 
  17. /* OUTPUT 
  18.  PrivateScope f() 
  19. */ 

是否感到有點奇怪,為什么這個時候調(diào)用的f()是基類中定義的,而不像上面所述的那樣,通過動態(tài)綁定,從而調(diào)用導(dǎo)出類PrivateOverride中定義的f()。不知道心細(xì)的你是否發(fā)現(xiàn),基類中f()方法的修飾是「private」。沒錯,這就是問題所在,PrivateOverride中定義的f()方法是一個全新的方法,因為private的緣故,對子類不可見,自然也不能被重載。

結(jié)論:

只有非 private 修飾的方法才可以被覆蓋

我們通過 Idea 寫代碼的時候,重寫的方法頭上可以標(biāo)注@Override注解,如果不是重寫的方法,標(biāo)注@Override注解就會報錯:

 

這樣也可以很好的提示我們非重寫方法,而是全新的方法。

「域帶來的失靈」:

當(dāng)小伙伴看到這里,就會開始認(rèn)為所有事物(除private修飾)都可以多態(tài)地發(fā)生。然而現(xiàn)實卻不是這樣子的,「只有普通的方法調(diào)用才可以是多態(tài)的」。這邊是多態(tài)的誤區(qū)所在。

讓我們再看看下面這組代碼:

  1. class Super { 
  2.     public int field = 0; 
  3.  
  4.     public int getField() { 
  5.         return field; 
  6.     } 
  7.  
  8. class Son extends Super { 
  9.     public int field = 1; 
  10.  
  11.     public int getField() { 
  12.         return field; 
  13.     } 
  14.  
  15.     public int getSuperField() { 
  16.         return super.field; 
  17.     } 
  18.  
  19. class FieldTest { 
  20.     public static void main(String[] args) { 
  21.         Super sup = new Son(); 
  22.         System.out.println("sup.field:" + sup.field + " sup.getField():" + sup.getField()); 
  23.  
  24.         Son son = new Son(); 
  25.         System.out.println("son.field:" + son.field + " son.getField:" + son.getField() + " son.getSupField:" + son.getSuperField()); 
  26.     } 
  27. /* OUTPUT 
  28. sup.field:0 sup.getField():1 
  29. son.field:1 son.getField:1 son.getSupField:0 
  30. */ 

從上面代碼中我們看到sup.field輸出的值不是 Son 對象中所定義的,而是Super本身定義的。這與我們認(rèn)識的多態(tài)有點沖突。

 

其實不然,當(dāng)Super對象轉(zhuǎn)型為Son引用時,任何域訪問操作都將由編譯器解析,因此不是多態(tài)的。在本例中,為Super.field和Son.field分配了不同的存儲空間,而Son類是從Super類導(dǎo)出的,因此,Son實際上是包含兩個稱為field的域:「它自己的+Super的」。

雖然這種問題看上去很令人頭痛,但是我們開發(fā)規(guī)范中,通常會將所有的域都設(shè)置為 private,這樣就不能直接訪問它們,只能通過調(diào)用方法來訪問。

「static 帶來的失靈」:

看到這里,小伙伴們應(yīng)該對多態(tài)有個大致的了解,但是不要掉以輕心哦,還有一種情況也是會出現(xiàn)失靈的,「那就是如果某個方法是靜態(tài)的,那么它的行為就不具有多態(tài)性?!?/p>

老規(guī)矩,我們看下這組代碼:

  1. class StaticSuper { 
  2.  
  3.     public static void staticTest() { 
  4.         System.out.println("StaticSuper staticTest()"); 
  5.     } 
  6.  
  7.  
  8. class StaticSon extends StaticSuper{ 
  9.  
  10.     public static void staticTest() { 
  11.         System.out.println("StaticSon staticTest()"); 
  12.     } 
  13.  
  14.  
  15. class StaticTest { 
  16.     public static void main(String[] args) { 
  17.         StaticSuper sup = new StaticSon(); 
  18.         sup.staticTest(); 
  19.     } 
  20. /* OUTPUT 
  21. StaticSuper staticTest() 
  22. */ 

「靜態(tài)方法是與類相關(guān)聯(lián),而非與對象相關(guān)聯(lián)」

3.構(gòu)造器與多態(tài)

首先我們需要明白的是構(gòu)造器不具有多態(tài)性,因為構(gòu)造器實際上是static方法,只不過該static的聲明是隱式的。

我們先回到開頭的那段神秘代碼:

 

其中輸出結(jié)果是:

  1. /* 
  2.     polygon() before cal() 
  3.     square.cal(), border = 0 
  4.     polygon() after cal() 
  5.     square.square(), border = 4 
  6. */ 

我們可以看到先輸出的是基類polygon中構(gòu)造器的方法。

這是因為基類的構(gòu)造器總是在導(dǎo)出類的構(gòu)造過程中被調(diào)用,而且是按照繼承層次逐漸向上鏈接,以使每個基類的構(gòu)造器都能得到調(diào)用。

 

因為構(gòu)造器有一項特殊的任務(wù):檢查對象是否能正確的被構(gòu)造。導(dǎo)出類只能訪問它自己的成員,不能訪問基類的成員(基類成員通常是private類型)。只有基類的構(gòu)造器才具有權(quán)限來對自己的元素進行初始化。因此,必須令所有構(gòu)造器都得到調(diào)用,否則就不可能正確構(gòu)造完整對象。

步驟如下:

  • 調(diào)用基類構(gòu)造器,這個步驟會不斷的遞歸下去,首先是構(gòu)造這種層次結(jié)構(gòu)的根,然后是下一層導(dǎo)出類,...,直到最底層的導(dǎo)出類
  • 按聲明順序調(diào)用成員的初始化方法
  • 調(diào)用導(dǎo)出類構(gòu)造器的主體

打個不是特別恰當(dāng)?shù)谋确剑耗愕某霈F(xiàn)是否先要有你父親,你父親的出現(xiàn)是否先要有你的爺爺,這就是逐漸向上鏈接的方式

構(gòu)造器內(nèi)部的多態(tài)行為有沒有想過如果在一個構(gòu)造器的內(nèi)調(diào)用正在構(gòu)造的對象的某個動態(tài)綁定方法,那么會發(fā)生什么情況呢?動態(tài)綁定的調(diào)用是在運行時才決定的,因為對象無法知道它是屬于方法所在的那個類還是那個類的導(dǎo)出類。如果要調(diào)用構(gòu)造器內(nèi)部的一個動態(tài)綁定方法,就要用到那個方法的被覆蓋后的定義。然而因為被覆蓋的方法在對象被完全構(gòu)造之前就會被調(diào)用,這可能就會導(dǎo)致一些難于發(fā)現(xiàn)的隱藏錯誤。

問題引索:

一個動態(tài)綁定的方法調(diào)用會向外深入到繼承層次結(jié)構(gòu)內(nèi)部,它可以調(diào)動導(dǎo)出類里的方法,如果我們是在構(gòu)造器內(nèi)部這樣做,那么就可能會調(diào)用某個方法,而這個方法做操縱的成員可能還未進行初始化,這肯定就會招致災(zāi)難的。

敏感的小伙伴是不是想到了開頭的那段代碼:


 

 

輸出結(jié)果是:

  1. /* 
  2.     polygon() before cal() 
  3.     square.cal(), border = 0 
  4.     polygon() after cal() 
  5.     square.square(), border = 4 
  6. */ 

我們在進行square對象初始化的時候,會先進行polygon對象的初始化,在polygon構(gòu)造器中有個cal()方法,這個時候就采用了動態(tài)綁定機制,調(diào)用了square的cal(),但這個時候border這個變量尚未進行初始化,int 類型的默認(rèn)值為 0,因此就有了square.cal(), border = 0的輸出??吹竭@里,小伙伴們是不是有種撥開云霧見青天的感覺!

這組代碼初始化的實際過程為:

  • 在其他任何事物發(fā)生之前,將分配給對象的存儲空間初始化成二進制的零
  • 調(diào)用基類構(gòu)造器時,會調(diào)用被覆蓋后的cal()方法,由于步驟1的緣故,因此 border 的值為 0
  • 按照聲明的順序調(diào)用成員的初始化方法
  • 調(diào)用導(dǎo)出類的構(gòu)造器主體

呼~終于復(fù)習(xí)完多態(tài)了,幸好是夢,沒人發(fā)現(xiàn)我的菜。不知道電腦手機前的你,是否跟小菜一樣呢,如果是的話趕緊跟小菜一起復(fù)習(xí),不讓別人發(fā)現(xiàn)自己還不會多態(tài)哦!

 

責(zé)任編輯:武曉燕 來源: 小菜良記
相關(guān)推薦

2023-11-02 08:25:25

組件ReactUI

2012-05-16 19:16:10

iPhone 5

2020-01-02 10:13:46

Redis數(shù)據(jù)庫字符串

2023-08-31 08:19:51

ViteSVGBase64

2011-02-21 16:47:03

Integer

2025-02-19 12:40:33

JWT

2021-09-07 11:20:02

binlogMySQL數(shù)據(jù)庫

2022-10-28 09:51:18

PrintfLinux開發(fā)

2015-12-31 13:38:59

創(chuàng)新大公司

2021-08-04 08:31:10

MySQL數(shù)據(jù)庫日志

2021-01-21 05:52:11

斷言assert程序員

2019-09-21 21:32:34

數(shù)據(jù)庫SQL分布式

2022-03-15 08:51:27

量子計算機量子加密普通加密

2011-11-23 13:05:01

2013-06-07 09:42:45

微信移動應(yīng)用營銷工具

2022-01-25 12:41:31

ChromeResponse接口

2023-11-02 07:29:06

2019-07-01 14:44:23

Java互聯(lián)網(wǎng)代碼

2020-03-25 10:44:16

位運算操作技巧

2020-08-05 14:28:32

編程程序員代碼
點贊
收藏

51CTO技術(shù)棧公眾號