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