面試題: 重載(Overload)和重寫(Override)的區(qū)別。重載的方法能否根據(jù)返回類型進(jìn)行區(qū)分
面試官考察點(diǎn)猜想
這道題純粹只是考查基礎(chǔ)理論知識(shí),對(duì)實(shí)際開發(fā)工作中沒有太多的指導(dǎo)意義碟绑,畢竟編輯器都有語法提示功能,如果沒寫正確茎匠,會(huì)有錯(cuò)誤提示格仲。
背景知識(shí)詳解
關(guān)于重載(Overload)和重寫(Override),在實(shí)際開發(fā)中使用非常頻繁诵冒,涉及到的背景知識(shí)并不難凯肋。
重寫
重寫是子類對(duì)父類的允許訪問的方法的實(shí)現(xiàn)過程進(jìn)行重新編寫, 返回值和形參都不能改變。即外殼不變汽馋,核心重寫侮东!
重寫是發(fā)生在類的繼承關(guān)系,或者類的實(shí)現(xiàn)關(guān)系中的豹芯,重寫后的方法和原方法需要保持完全相同的返回值類型悄雅、方法名、參數(shù)個(gè)數(shù)以及參數(shù)類型铁蹈,簡單來說宽闲,就是子類重寫的方法必須和父類保持完全一致
類的繼承關(guān)系
我們來看下面這個(gè)基于繼承關(guān)系的例子。
class Animal{
public void move(){
System.out.println("動(dòng)物可以移動(dòng)");
}
}
class Bird extends Animal{
public void move(){
System.out.println("鳥可以飛");
}
}
class Dog extends Animal{
public void move(){
System.out.println("狗可以跑")
}
}
public class TestMain{
public static void main(String args[]){
Animal a = new Animal(); // Animal 對(duì)象
Animal b = new Bird(); //Bird對(duì)象
Animal c = new Dog(); // Dog 對(duì)象
a.move();// 執(zhí)行 Animal 類的方法
b.move(); //執(zhí)行Bird類的方法
c.move();//執(zhí)行 Dog 類的方法
}
}
上述程序運(yùn)行的結(jié)果
動(dòng)物可以移動(dòng)
鳥可以飛
狗可以跑
在這個(gè)案例中握牧,Animal
是一個(gè)屬于動(dòng)物的抽象類容诬,它定義了一個(gè)方法move()
。表示動(dòng)物的具有的行為沿腰。
而動(dòng)物只是一個(gè)泛類別览徒,具體到某種動(dòng)物時(shí),行為方式是不同的颂龙,因此定義了Bird
和Doc
习蓬,分別繼承了Animal
這個(gè)類纽什,并且重寫了move()
方法,分別實(shí)現(xiàn)這兩種動(dòng)物的行為方式友雳。
重寫的好處在于子類可以根據(jù)需要稿湿,定義特定于自己的行為。 也就是說子類能夠根據(jù)需要實(shí)現(xiàn)父類的方法押赊。
在類繼承關(guān)系中饺藤,父類的非抽象方法,子類是不強(qiáng)制要求重寫的流礁。在實(shí)際應(yīng)用中涕俗,如果重寫了父類的方法,并且實(shí)例對(duì)象的引用指向的是子類時(shí)神帅,JVM會(huì)自動(dòng)調(diào)用子類重寫的方法再姑,此時(shí),父類的方法完全被屏蔽了找御。就像前面測(cè)試的代碼元镀。
父類引用指向子類實(shí)現(xiàn)Dog()
,此時(shí)調(diào)用c.move()
方法霎桅,只會(huì)調(diào)用到Dog
類中的move()
方法栖疑。如果Dog
子類沒有重寫move()
方法,則會(huì)調(diào)用父類Animal
的move()
方法滔驶。
Animal c = new Dog(); // Dog 對(duì)象
c.move();//執(zhí)行 Dog 類的方法
在有些情況下遇革,子類重寫了父類的方法,我們希望在調(diào)用子類重寫方法的同時(shí)揭糕,仍然能夠調(diào)用到父類被重寫的方法萝快,怎么實(shí)現(xiàn)?
Super關(guān)鍵字
當(dāng)需要在子類中調(diào)用父類的被重寫方法時(shí)著角,要使用 super 關(guān)鍵字揪漩。
class Animal{
public void move(){
System.out.println("動(dòng)物可以移動(dòng)");
}
}
class Bird extends Animal{
public void move(){
super.move(); //增加super調(diào)用
System.out.println("鳥可以飛");
}
}
}
public class TestMain{
public static void main(String args[]){
Animal b = new Bird(); //Bird對(duì)象
b.move();//執(zhí)行 Bird 類的方法
}
}
運(yùn)行結(jié)果如下:
動(dòng)物可以移動(dòng)
鳥可以飛
方法的重寫規(guī)則
總結(jié)一下,在Java中吏口,方法重寫的規(guī)則奄容。
- 參數(shù)列表與被重寫方法的參數(shù)列表必須完全相同。
- 返回類型與被重寫方法的返回類型可以不相同锨侯,但是必須是父類返回值的派生類(java5 及更早版本返回類型要一樣嫩海,java7 及更高版本可以不同)冬殃。
- 訪問權(quán)限不能比父類中被重寫的方法的訪問權(quán)限更低囚痴。例如:如果父類的一個(gè)方法被聲明為 public,那么在子類中重寫該方法就不能聲明為 protected审葬。
- 父類的成員方法只能被它的子類重寫深滚。
- 聲明為 final 的方法不能被重寫奕谭。
- 聲明為 static 的方法不能被重寫,但是能夠被再次聲明痴荐。
- 子類和父類在同一個(gè)包中血柳,那么子類可以重寫父類所有方法,除了聲明為 private 和 final 的方法生兆。
- 子類和父類不在同一個(gè)包中难捌,那么子類只能夠重寫父類的聲明為 public 和 protected 的非 final 方法。
- 重寫的方法能夠拋出任何非強(qiáng)制異常鸦难,無論被重寫的方法是否拋出異常根吁。但是,重寫的方法不能拋出新的強(qiáng)制性異常合蔽,或者比被重寫方法聲明的更廣泛的強(qiáng)制性異常击敌,反之則可以。
- 構(gòu)造方法不能被重寫拴事。
- 如果不能繼承一個(gè)類沃斤,則不能重寫該類的方法。
基于接口實(shí)現(xiàn)的重寫
基于接口實(shí)現(xiàn)的重寫刃宵,在實(shí)際應(yīng)用中衡瓶,使用非常頻繁,以線程實(shí)現(xiàn)為例组去,如圖所示鞍陨,表示Thread和Runnable的類關(guān)系圖。
Runnable是一個(gè)接口从隆,它定義了線程的執(zhí)行方法诚撵,代碼如下:
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
在實(shí)際應(yīng)用中,我們可以直接繼承這個(gè)接口來聲明一個(gè)線程键闺。
Thread寿烟,是一個(gè)普通的線程類,它實(shí)現(xiàn)了Runnable接口辛燥,并且重寫了Runnable這個(gè)接口的run
方法筛武,這里這么設(shè)計(jì)的目的是: 避免Java中一個(gè)類只能實(shí)現(xiàn)一個(gè)接口這一規(guī)則
導(dǎo)致,如果一個(gè)類已經(jīng)繼承了其他的接口挎塌,但是又想要去實(shí)現(xiàn)線程時(shí)的問題徘六。
public
class Thread implements Runnable {
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
由于接口只是用來做規(guī)范設(shè)計(jì),用來描述某個(gè)對(duì)象具有什么行為榴都,但是它并沒有具體的實(shí)現(xiàn)待锈,因此如果需要聲明一個(gè)線程,就需要實(shí)現(xiàn)該接口并且重寫里面的抽象方法(接口中未實(shí)現(xiàn)的方法都是抽象的嘴高,子類必須要重寫)竿音。
Thread類中重寫了Runnable中的run
方法和屎,該方法調(diào)用了target.run()
。這個(gè)target
是真正的線程業(yè)務(wù)實(shí)現(xiàn)春瞬,Thread只是一個(gè)委派設(shè)計(jì)模式柴信。
因此,如果我們想通過繼承Thread
來實(shí)現(xiàn)線程宽气,則需要按照如下代碼的寫法來實(shí)現(xiàn)随常,其中target
就是代表著子類的App
這個(gè)對(duì)象實(shí)例。
public class App extends Thread{
@Override
public void run() {
//doSomething
}
}
由于接口只是一種行為規(guī)范萄涯,本身不提供實(shí)現(xiàn)线罕,因此實(shí)現(xiàn)接口的子類,都“必須”要重寫父類的方法窃判,這個(gè)和類繼承是有區(qū)別的钞楼。
重載
重載(overloading) 是在一個(gè)類里面,方法名字相同袄琳,而參數(shù)不同询件。返回類型可以相同也可以不同。
每個(gè)重載的方法(或者構(gòu)造函數(shù))都必須有一個(gè)獨(dú)一無二的參數(shù)類型列表唆樊。
最常用的地方就是構(gòu)造器的重載宛琅,比如在ThreadPoolExecutor
線程池的實(shí)現(xiàn)類中,可看到如下的重載方法逗旁。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}
方法重載的好處就是讓類以統(tǒng)一的方式處理不同類型的一種手段嘿辟,調(diào)用方法時(shí)通過傳遞給他們的不同個(gè)數(shù)和類型的參數(shù)來決定具體使用哪個(gè)方法,這就是多態(tài)性片效。
它的特點(diǎn)是:重載發(fā)生在本類红伦,方法名相同,參數(shù)列表不同淀衣,與返回值無關(guān)昙读,只和方法名,參數(shù)的類型相關(guān)膨桥。
方法重載時(shí)蛮浑,方法之間需要存在一定的聯(lián)系,因?yàn)檫@樣可以提高程序的可讀性只嚣,并且我們一般只重載功能相似的方法沮稚。
重載規(guī)則
- 被重載的方法必須改變參數(shù)列表(參數(shù)個(gè)數(shù)或類型不一樣);
- 被重載的方法可以改變返回類型册舞;
- 被重載的方法可以改變?cè)L問修飾符蕴掏;
- 被重載的方法可以聲明新的或更廣的檢查異常;
- 方法能夠在同一個(gè)類中或者在一個(gè)子類中被重載。
- 無法以返回值類型作為重載函數(shù)的區(qū)分標(biāo)準(zhǔn)囚似。
問題解答
理解了上述知識(shí)點(diǎn)以后,再來看這道面試題线得。
面試題: 重載(Overload)和重寫(Override)的區(qū)別饶唤。重載的方法能否根據(jù)返回類型進(jìn)行區(qū)分
區(qū)別:
- 方法重載是一個(gè)類中定義了多個(gè)方法名相同,而他們的參數(shù)的數(shù)量不同或數(shù)量相同而類型和次序不同,則稱為方法的重載(Overloading)。
- 方法重寫是在子類存在方法與父類的方法的名字相同,而且參數(shù)的個(gè)數(shù)與類型一樣,返回值也一樣的方法,就稱為重寫(Overriding)贯钩。
- 方法重載是一個(gè)類的多態(tài)性表現(xiàn),而方法重寫是子類與父類的一種多態(tài)性表現(xiàn)募狂。
重載方法是否能夠根據(jù)返回類型進(jìn)行區(qū)分
重載方法無法根據(jù)類型來區(qū)分, 它只能通過參數(shù)類型角雷、參數(shù)個(gè)數(shù)來區(qū)分祸穷,但是對(duì)于重載的方法,是允許修改返回值類型勺三、異常類型雷滚、訪問等級(jí),但是不能只根據(jù)這些類型類做重載吗坚。
為什們不能僅根據(jù)返回類型來區(qū)分重載呢祈远?
原因是,在調(diào)用目標(biāo)方法時(shí)商源,是無法指定返回值類型信息的车份,這個(gè)時(shí)候編譯器并不知道你要調(diào)用哪個(gè)函數(shù)。
比如在下面這段代碼中牡彻,當(dāng)調(diào)用max(1,2);時(shí)無法確定調(diào)用的是哪個(gè)扫沼,單從這一點(diǎn)上來說,僅返回值類型不同的重載是不應(yīng)該允許的庄吼。
float max(int a, int b);
int max(int a, int b);
可能有同學(xué)會(huì)問缎除,如果讓編譯器能夠根據(jù)上下文語境來判斷呢?比如像下面這段代碼总寻。
float x=max(1,2);
int y=max(2,3);
在實(shí)際開發(fā)中伴找,很多時(shí)候會(huì)存在這樣一種方法調(diào)用max(1,2)
,并不會(huì)去聲明返回值废菱,由于這種情況的存在技矮,所以這個(gè)理論也不能實(shí)現(xiàn)。
函數(shù)的返回值只是作為函數(shù)運(yùn)行之后的一個(gè)“狀態(tài)”他是保持方法的調(diào)用者與被調(diào)用者進(jìn)行通信的關(guān)鍵殊轴。并不能作為某個(gè)方法的“標(biāo)識(shí)”
問題總結(jié)
這個(gè)問題衰倦,其實(shí)是屬于那種,你不問我旁理,我一定會(huì)認(rèn)為自己知道樊零,而且在工作開發(fā)中也能使用不會(huì)出問題,但是你一問我,我一定會(huì)懵逼驻襟,不是因?yàn)檎娴牟欢峒瑁遣恢涝趺慈ソM織語言來描述這兩個(gè)概念。
建議大家參考“費(fèi)曼學(xué)習(xí)法”沉衣,就是把這篇文章學(xué)到的理論郁副,通過演講的方式表達(dá)出來,可以和同事豌习,或者自己自問自答存谎。