方法重写(Java篇)
很多人會(huì)問:
當(dāng)一個(gè)子類繼承一個(gè)父類時(shí),它同時(shí)繼承了父類的屬性和方法。子類可以直接使用父類的屬性和方法,如果父類的方法不能滿足子類的需求,則可以在子類中對(duì)父類的方法進(jìn)行重寫(或覆蓋)。
在方法重寫時(shí),如果子類需要引用父類中原有的方法,可以使用 super 關(guān)鍵字。當(dāng)子類重寫父類方法后,在子類對(duì)象使用該方法時(shí),會(huì)執(zhí)行子類中重寫的方法。如果子類型重寫了父類型的同名方法,那么只知道父類型的定義就可以調(diào)用子類型的方法了
實(shí)際應(yīng)用中,用得最多的一種運(yùn)行時(shí)多態(tài),就是用只知道父類型(可能是類,更多的可能是接口)的定義,這樣只能調(diào)用父類型的方法(這在大型軟件里很常用,父類型和子類型可能是不同的人編寫的,以利于協(xié)作編程)。
簡約規(guī)則:
在子類重寫父類方法時(shí),需要遵守以下幾個(gè)重寫規(guī)則。
□ 重寫方法名、參數(shù)和返回類型必須與父類方法定義一致。
□ 重寫方法的修飾符不能比父類方法嚴(yán)格。例如父類方法時(shí)用 public 修飾,那么重寫方法不能使用protected或private等修飾。
□ 重寫方法如果有throws 定義,那么重寫方法throws 的異常類型可以是父類方法throws的異常類型及其子類類型。
方法在重寫時(shí)有很多細(xì)節(jié)需要注意,否則即使定義了方法,也可能不屬于重寫,不具有方法重寫之后的特征。
重寫的規(guī)則:
1、重寫規(guī)則之一:
訪問修飾符的限制一定要不小于被重寫方法的訪問修飾符
2、重寫規(guī)則之二:
參數(shù)列表必須與被重寫方法的相同。
重載的時(shí)候,方法名要一樣,但是參數(shù)類型和個(gè)數(shù)不一樣,返回值類型可以相同也可以不相同。
3、重寫規(guī)則之三:
C-1:返回類型必須與被重寫方法的返回類型相同。
父類方法A:void catch(){} 子類方法 B:int catch(){} 兩者雖然參數(shù)相同, 返回類型不同, 所以不是重寫。
父類方法A:int catch(){} 子類方法 B:long catch(){} 返回類型雖然兼容父類, 但是不同就是不同, 所以不是重寫。
即:如果重寫方法的參數(shù)列表和方法名相同,且其他條件滿足的情況下,方法的返回值為父類的子類,那么該方法也為重寫方法
package com.ibm.dietime1943.test;
public class Computer {
public Computer sale() { return new Computer(); }
public HP make() { return new HP(); }
}
class IBM extends Computer {
@Override
public IBM sale() { return new IBM(); }
}
class HP extends Computer {
@Override
public Computer make() { return new Computer(); } // compilation error
}
4、重寫規(guī)則之四:
重寫方法不能拋出新的異常或者比被重寫方法聲明的檢查異常更廣的檢查異常。但是可以拋出更少,更有限或者不拋出異常。
5、重寫規(guī)則之五:
如果一個(gè)方法不能被繼承, 則不能重寫它。
比較典型的就是父類的private方法。因?yàn)閜rivate說明該方法對(duì)子類是不可見的, 子類再寫一個(gè)同名的方法并不是對(duì)父類方法進(jìn)行復(fù)寫(Override), 而是重新生成一個(gè)新的方法, 也就不存在多態(tài)的問題了。同理也可以解釋final, 因?yàn)榉椒ㄍ瑯邮遣豢筛采w的。
6、重寫規(guī)則之六:
不能重寫被標(biāo)識(shí)為final的方法。
7、重寫規(guī)則之七:
靜態(tài)方法不能被重寫。
《JAVA編程思想》中多次的提到:方法是靜態(tài)的、他的行為就不具有多態(tài)性。靜態(tài)方法是與類、而非單個(gè)對(duì)象相關(guān)聯(lián)的。
父類的普通方法可以被繼承和重寫,不多作解釋,如果子類繼承父類,而且子類沒有重寫父類的方法,但是子類會(huì)有從父類繼承過來的方法。靜態(tài)的方法可以被繼承,但是不能重寫。如果父類中有一個(gè)靜態(tài)的方法,子類也有一個(gè)與其方法名,參數(shù)類型,參數(shù)個(gè)數(shù)都一樣的方法,并且也有static關(guān)鍵字修飾,那么該子類的方法會(huì)把原來繼承過來的父類的方法隱藏,而不是重寫。通俗的講就是父類的方法和子類的方法是兩個(gè)沒有關(guān)系的方法,具體調(diào)用哪一個(gè)方法是看是哪個(gè)對(duì)象的引用;這種父子類方法也不在存在多態(tài)的性質(zhì)。《JAVA編程思想》:只有普通的方法調(diào)用可以是多態(tài)的,靜態(tài)方法是與類而不是與某個(gè)對(duì)象相關(guān)聯(lián)。
// 2016/11/22 16:45 bluetata 追記 add Start補(bǔ)足1:父類的靜態(tài)方法不能被子類覆蓋為非靜態(tài)方法。
子類可以定義于父類的靜態(tài)方法同名的靜態(tài)方法、以便在子類中隱藏父類的靜態(tài)方法(滿足覆蓋約束)、而且Java虛擬機(jī)把靜態(tài)方法和所屬的類綁定、而把實(shí)例方法和所屬的實(shí)例綁定。如果在上記的方法上追記@Override注解的話、該方法會(huì)出編譯錯(cuò)誤。應(yīng)為該方法實(shí)際不是重寫方法。補(bǔ)足2:父類的非靜態(tài)方法不能被子類覆蓋為靜態(tài)方法。
// 2016/11/22 16:45 bluetata 追記 add End補(bǔ)足3:面試可能會(huì)遇到的此處相關(guān)問題(與靜態(tài)相關(guān))
1、abstract方法能否被static修飾?不能被static修飾, 因?yàn)槌橄蠓椒ㄒ恢貙?、而static和子類占不到邊、即上述。// 2016/12/06 20:59 bluetata 追記反過來也一樣static方法一定不能被abstract方法修飾, static不屬于對(duì)象而屬于類, static方法可以被類直接調(diào)用(抽象方法需要被實(shí)例才能被調(diào)用, 這里說的實(shí)例是實(shí)現(xiàn)的意思,也就是重寫后實(shí)現(xiàn)其方法), 這樣注定了static方法一定有方法體, 不能是沒有方法體的抽象方法(被abstract修飾) // 2018/07/10 18:17 bluetata 追記2、為什么靜態(tài)方法不能被覆蓋? // 2016/12/15 午后 追記可以參看上面從java編程思想摘出的話、另外在總結(jié)下:覆蓋依賴于類的實(shí)例,而靜態(tài)方法和類實(shí)例并沒有什么關(guān)系。而且靜態(tài)方法在編譯時(shí)就已經(jīng)確定,而方法覆蓋是在運(yùn)行時(shí)確定的(動(dòng)態(tài)綁定)(也可以說是java多態(tài)體現(xiàn)在運(yùn)行時(shí)、而static在編譯時(shí)、與之相悖)。3、構(gòu)造方法能否被重寫、為什么? // 2016/12/15 晚 追記不能、構(gòu)造方法是隱式的static方法、同問題2。其實(shí)這個(gè)問題回答切入點(diǎn)很多、首先構(gòu)造方法無返回值、方法名必須和所在類名相同、這一點(diǎn)就必殺了子類無法重寫父類構(gòu)造方法。另外多態(tài)方面、重寫是多態(tài)的一種提現(xiàn)方式、假設(shè)在子類重寫了構(gòu)造方法是成立的、那么子類何談實(shí)例成父類。另外重要得一點(diǎn)、子類可以使用super()調(diào)用父類的構(gòu)造方法、且必須放在子類構(gòu)造方法內(nèi)的第一行。 請(qǐng)參看另一篇博文: <<Super和this用法,對(duì)象的加載順序>>4、靜態(tài)方法為什么不能訪問非靜態(tài)變量或方法? // 2018/07/10 午后追記對(duì)于前面123問題理解后, 問題4也不難理解, 還是引用下《JAVA編程思想》:靜態(tài)方法是與類而不是與某個(gè)對(duì)象相關(guān)聯(lián) 用static修飾的成員屬于類, 非static修飾的成員屬于實(shí)例對(duì)象, 也就是類可以直接調(diào)用靜態(tài)成員, 這樣假設(shè)如果類直接調(diào)用了靜態(tài)成員, 而靜態(tài)成員調(diào)用了非靜態(tài)變量或方法, 這樣在內(nèi)存中是找不到該非靜態(tài)變量方法的, 因?yàn)殪o態(tài)方法需要?jiǎng)?chuàng)建對(duì)象后才可調(diào)用.另外通過類加載說明: 類的加載全過程:加載->驗(yàn)證->準(zhǔn)備->解析->初始化 在這里加載到解析階段都是JVM進(jìn)行主導(dǎo),而在初始化階段才是真正java執(zhí)行代碼的階段. static成員在初始化階段之前會(huì)被加載到方法區(qū)中, 并且進(jìn)行初始化賦值等操作,并且分配內(nèi)存, 而非static成員確是在加載后解析后的初始化階段才會(huì)被"加載"分配內(nèi)存, 也就是代碼中使用new進(jìn)行創(chuàng)建實(shí)例的時(shí)候, 這樣也就驗(yàn)證了 類可以直接調(diào)用static成員沒有問題, 而直接調(diào)用非static的成員就會(huì)出問題, 因?yàn)檫`背了java加載初始化的邏輯.注意: 如果static調(diào)用非static成員 編譯器會(huì)出現(xiàn) No enclosing instance of type * is accessible 異常錯(cuò)誤.
XX01、重寫規(guī)則補(bǔ)足:
補(bǔ)足1:父類的抽象方法可以被子類通過兩種途徑覆蓋(即實(shí)現(xiàn)和覆蓋)。
補(bǔ)足2:父類的非抽象方法可以被覆蓋為抽象方法[2]。
[2]子類必須為抽象類。package com.ibm.dietime1943.test;
public class Computer {
public Computer send() { return new Computer();}
}
abstract class Lenovo extends Computer {
@Override
public abstract Computer send();
}
以上規(guī)則更加詳細(xì)的說明請(qǐng)參看另一篇博文: <<JAVA中 @Override 的作用>>
// 2016/11/21 20:27 bluetata 追記標(biāo)注補(bǔ)足1(上接博文后追記)
舉例(來源于OCJP題庫):
Given:
Which five methods, inserted independently at line 5, will compile? (Choose five.)
A. public int blipvert(int x) { return 0; }
B. private int blipvert(int x) { return 0; }
C. private int blipvert(long x) { return 0; }
D. protected long blipvert(int x) { return 0; }
E. protected int blipvert(long x) { return 0; }
F. protected long blipvert(long x) { return 0; }
G. protected long blipvert(int x, int y) { return 0; }
Answer: A,C,E,F,G
Explanation:繼承關(guān)系后,子類重寫父類的方法時(shí),修飾符作用域不能小于父類被重寫方法,所以A正確,B不正確。選項(xiàng)CEFG均不滿足重寫規(guī)則,不是重寫方法(在子類的普通方法)。選項(xiàng)D即為不滿足C-1補(bǔ)足。
里氏替換原則
這項(xiàng)原則最早是在1987年、由麻省理工學(xué)院的由芭芭拉·利斯科夫(Barbara Liskov)在一次會(huì)議上名為“數(shù)據(jù)的抽象與層次”的演說中首先提出。里氏替換原則的內(nèi)容可以描述為: “派生類(子類)對(duì)象能夠替換其基類(超類)對(duì)象被使用?!?以上內(nèi)容并非利斯科夫的原文,而是譯自羅伯特·馬丁(Robert Martin)對(duì)原文的解讀。其原文為:Let q(x) be a property provable about objectsx of type T. Thenq(y) should be true for objectsy of typeS where S is a subtype ofT.嚴(yán)格的定義:如果對(duì)每一個(gè)類型為T1的對(duì)象o1、都有類型為T2的對(duì)象o2、使得以T1定義的所有程序P在所有的對(duì)象o1都換成o2時(shí)、程序P的行為沒有變化、那么類型T2是類型T1的子類型。通俗的定義:所有引用基類的地方必須能透明地使用其子類的對(duì)象。更通俗的定義:子類可以擴(kuò)展父類的功能,但不能改變父類原有的功能。里氏替換原則包含以下4層含義:1、子類可以實(shí)現(xiàn)父類的抽象方法、但是不能[1]覆蓋父類的非抽象方法。(核心)[參照1]在我們做系統(tǒng)設(shè)計(jì)時(shí)、經(jīng)常會(huì)設(shè)計(jì)接口或抽象類、然后由子類來實(shí)現(xiàn)抽象方法、這里使用的其實(shí)就是里氏替換原則。子類可以實(shí)現(xiàn)父類的抽象方法很好理解、事實(shí)上子類也必須完全實(shí)現(xiàn)父類的抽象方法、哪怕寫一個(gè)空方法、否則會(huì)編譯報(bào)錯(cuò)。
里氏替換原則的關(guān)鍵點(diǎn)在于不能覆蓋父類的非抽象方法。父類中凡是已經(jīng)實(shí)現(xiàn)好的方法、實(shí)際上是在設(shè)定一系列的規(guī)范和契約、雖然它不強(qiáng)制要求所有的子類必須遵從這些規(guī)范、但是如果子類對(duì)這些非抽象方法任意修改、就會(huì)對(duì)整個(gè)繼承體系造成破壞。而里氏替換原則就是表達(dá)了這一層含義。
[1]處的說明:該處的不建議原則、并不是硬性規(guī)定無法不能的含義。增加新功能時(shí)、盡量添加新方法實(shí)現(xiàn)、而不是(不建議)去重寫父類的方法、也不建議重載父類的方法。// 2016/11/22 15:33 bluetata 追記
2、子類中可以增加自己特有的方法。3、當(dāng)子類重寫或?qū)崿F(xiàn)父類的方法時(shí),方法的前置條件(即方法的形參)要比父類方法的輸入?yún)?shù)更寬松。4、當(dāng)子類的方法重寫或?qū)崿F(xiàn)父類的方法時(shí),方法的后置條件(即方法的返回值)要比父類更嚴(yán)格。[參照2]// 2016/11/22 18:54 bluetata 追記 add Start - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -追記來源/Michael727(簡書作者)。原文鏈接:http://www.jianshu.com/p/2aa66a36af26里氏替換原則的核心是抽象,抽象又依賴于繼承這個(gè)特性,在OOP當(dāng)中,繼承的優(yōu)缺點(diǎn)都相當(dāng)?shù)拿黠@。繼承的優(yōu)點(diǎn):
①、代碼重用,減少創(chuàng)建的成本,每個(gè)子類擁有父類的方法和屬性。②、子類和父類基本相似,但又與父類有所區(qū)別。③、提高代碼的可擴(kuò)展性,實(shí)現(xiàn)父類的方法就可以了,很多開源框架的擴(kuò)展接口都是通過繼承父類完成的。④、提高產(chǎn)品或項(xiàng)目的開放性。繼承的缺點(diǎn):。
①、繼承是侵入性的,只要繼承就必須擁有父類的所有屬性和方法。②、可能造成子類代碼冗余、靈活性降低,因?yàn)樽宇惐仨殦碛懈割惖膶傩院头椒?。③、增?qiáng)了耦合性。當(dāng)父類的常量、變量和方法被修改時(shí),必須考慮子類的修改,而且在缺乏規(guī)范的環(huán)境下,這種修好可能帶來非常糟糕的結(jié)果:大片的代碼需要重構(gòu)。
總結(jié)
以上是生活随笔為你收集整理的方法重写(Java篇)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2499元起!真我GT Neo5发布:全
- 下一篇: 240W充电有多强?80秒将手机电池从1