面試題: 多載(Overload)和重寫(Override)的區別,多載的方法能否根據回傳型別進行區分
面試官考察點猜想
這道題純粹只是考查基礎理論知識,對實際開發作業中沒有太多的指導意義,畢竟編輯器都有語法提示功能,如果沒寫正確,會有錯誤提示,
背景知識詳解
關于多載(Overload)和重寫(Override),在實際開發中使用非常頻繁,涉及到的背景知識并不難,
重寫
重寫是子類對父類的允許訪問的方法的實作程序進行重新撰寫, 回傳值和形參都不能改變,即外殼不變,核心重寫!
重寫是發生在類的繼承關系,或者類的實作關系中的,重寫后的方法和原方法需要保持完全相同的回傳值型別、方法名、引數個數以及引數型別,簡單來說,就是子類重寫的方法必須和父類保持完全一致
類的繼承關系
我們來看下面這個基于繼承關系的例子,
class Animal{
public void move(){
System.out.println("動物可以移動");
}
}
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 物件
Animal b = new Bird(); //Bird物件
Animal c = new Dog(); // Dog 物件
a.move();// 執行 Animal 類的方法
b.move(); //執行Bird類的方法
c.move();//執行 Dog 類的方法
}
}
上述程式運行的結果
動物可以移動
鳥可以飛
狗可以跑
在這個案例中,Animal是一個屬于動物的抽象類,它定義了一個方法move(),表示動物的具有的行為,
而動物只是一個泛類別,具體到某種動物時,行為方式是不同的,因此定義了Bird和Doc,分別繼承了Animal這個類,并且重寫了move()方法,分別實作這兩種動物的行為方式,
重寫的好處在于子類可以根據需要,定義特定于自己的行為, 也就是說子類能夠根據需要實作父類的方法,
在類繼承關系中,父類的非抽象方法,子類是不強制要求重寫的,在實際應用中,如果重寫了父類的方法,并且實體物件的參考指向的是子類時,JVM會自動呼叫子類重寫的方法,此時,父類的方法完全被屏蔽了,就像前面測驗的代碼,
父類參考指向子類實作Dog(),此時呼叫c.move()方法,只會呼叫到Dog類中的move()方法,如果Dog子類沒有重寫move()方法,則會呼叫父類Animal的move()方法,
Animal c = new Dog(); // Dog 物件
c.move();//執行 Dog 類的方法
在有些情況下,子類重寫了父類的方法,我們希望在呼叫子類重寫方法的同時,仍然能夠呼叫到父類被重寫的方法,怎么實作?
Super關鍵字
當需要在子類中呼叫父類的被重寫方法時,要使用 super 關鍵字,
class Animal{
public void move(){
System.out.println("動物可以移動");
}
}
class Bird extends Animal{
public void move(){
super.move(); //增加super呼叫
System.out.println("鳥可以飛");
}
}
}
public class TestMain{
public static void main(String args[]){
Animal b = new Bird(); //Bird物件
b.move();//執行 Bird 類的方法
}
}
運行結果如下:
動物可以移動
鳥可以飛
方法的重寫規則
總結一下,在Java中,方法重寫的規則,
- 引數串列與被重寫方法的引數串列必須完全相同,
- 回傳型別與被重寫方法的回傳型別可以不相同,但是必須是父類回傳值的派生類(java5 及更早版本回傳型別要一樣,java7 及更高版本可以不同),
- 訪問權限不能比父類中被重寫的方法的訪問權限更低,例如:如果父類的一個方法被宣告為 public,那么在子類中重寫該方法就不能宣告為 protected,
- 父類的成員方法只能被它的子類重寫,
- 宣告為 final 的方法不能被重寫,
- 宣告為 static 的方法不能被重寫,但是能夠被再次宣告,
- 子類和父類在同一個包中,那么子類可以重寫父類所有方法,除了宣告為 private 和 final 的方法,
- 子類和父類不在同一個包中,那么子類只能夠重寫父類的宣告為 public 和 protected 的非 final 方法,
- 重寫的方法能夠拋出任何非強制例外,無論被重寫的方法是否拋出例外,但是,重寫的方法不能拋出新的強制性例外,或者比被重寫方法宣告的更廣泛的強制性例外,反之則可以,
- 構造方法不能被重寫,
- 如果不能繼承一個類,則不能重寫該類的方法,
基于介面實作的重寫
基于介面實作的重寫,在實際應用中,使用非常頻繁,以執行緒實作為例,如圖所示,表示Thread和Runnable的類關系圖,

Runnable是一個介面,它定義了執行緒的執行方法,代碼如下:
@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();
}
在實際應用中,我們可以直接繼承這個介面來宣告一個執行緒,
Thread,是一個普通的執行緒類,它實作了Runnable介面,并且重寫了Runnable這個介面的run方法,這里這么設計的目的是: 避免Java中一個類只能實作一個介面這一規則導致,如果一個類已經繼承了其他的介面,但是又想要去實作執行緒時的問題,
public
class Thread implements Runnable {
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
由于介面只是用來做規范設計,用來描述某個物件具有什么行為,但是它并沒有具體的實作,因此如果需要宣告一個執行緒,就需要實作該介面并且重寫里面的抽象方法(介面中未實作的方法都是抽象的,子類必須要重寫),
Thread類中重寫了Runnable中的run方法,該方法呼叫了target.run(),這個target是真正的執行緒業務實作,Thread只是一個委派設計模式,
因此,如果我們想通過繼承Thread來實作執行緒,則需要按照如下代碼的寫法來實作,其中target就是代表著子類的App這個物件實體,
public class App extends Thread{
@Override
public void run() {
//doSomething
}
}
由于介面只是一種行為規范,本身不提供實作,因此實作介面的子類,都“必須”要重寫父類的方法,這個和類繼承是有區別的,
多載
多載(overloading) 是在一個類里面,方法名字相同,而引數不同,回傳型別可以相同也可以不同,
每個多載的方法(或者建構式)都必須有一個獨一無二的引數型別串列,
最常用的地方就是構造器的多載,比如在ThreadPoolExecutor執行緒池的實作類中,可看到如下的多載方法,
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) {}
方法多載的好處就是讓類以統一的方式處理不同型別的一種手段,呼叫方法時通過傳遞給他們的不同個數和型別的引數來決定具體使用哪個方法,這就是多型性,
它的特點是:多載發生在本類,方法名相同,引數串列不同,與回傳值無關,只和方法名,引數的型別相關,
方法多載時,方法之間需要存在一定的聯系,因為這樣可以提高程式的可讀性,并且我們一般只多載功能相似的方法,
多載規則
- 被多載的方法必須改變引數串列(引數個數或型別不一樣);
- 被多載的方法可以改變回傳型別;
- 被多載的方法可以改變訪問修飾符;
- 被多載的方法可以宣告新的或更廣的檢查例外;
- 方法能夠在同一個類中或者在一個子類中被多載,
- 無法以回傳值型別作為多載函式的區分標準,
問題解答
理解了上述知識點以后,再來看這道面試題,
面試題: 多載(Overload)和重寫(Override)的區別,多載的方法能否根據回傳型別進行區分
區別:
- 方法多載是一個類中定義了多個方法名相同,而他們的引數的數量不同或數量相同而型別和次序不同,則稱為方法的多載(Overloading),
- 方法重寫是在子類存在方法與父類的方法的名字相同,而且引數的個數與型別一樣,回傳值也一樣的方法,就稱為重寫(Overriding),
- 方法多載是一個類的多型性表現,而方法重寫是子類與父類的一種多型性表現,
多載方法是否能夠根據回傳型別進行區分
多載方法無法根據型別來區分, 它只能通過引數型別、引數個數來區分,但是對于多載的方法,是允許修改回傳值型別、例外型別、訪問等級,但是不能只根據這些型別類做多載,
為什們不能僅根據回傳型別來區分多載呢?
原因是,在呼叫目標方法時,是無法指定回傳值型別資訊的,這個時候編譯器并不知道你要呼叫哪個函式,
比如在下面這段代碼中,當呼叫max(1,2);時無法確定呼叫的是哪個,單從這一點上來說,僅回傳值型別不同的多載是不應該允許的,
float max(int a, int b);
int max(int a, int b);
可能有同學會問,如果讓編譯器能夠根據背景關系語境來判斷呢?比如像下面這段代碼,
float x=max(1,2);
int y=max(2,3);
在實際開發中,很多時候會存在這樣一種方法呼叫max(1,2),并不會去宣告回傳值,由于這種情況的存在,所以這個理論也不能實作,
函式的回傳值只是作為函式運行之后的一個“狀態”他是保持方法的呼叫者與被呼叫者進行通信的關鍵,并不能作為某個方法的“標識”
問題總結
這個問題,其實是屬于那種,你不問我,我一定會認為自己知道,而且在作業開發中也能使用不會出問題,但是你一問我,我一定會懵逼,不是因為真的不懂,而是不知道怎么去組織語言來描述這兩個概念,
建議大家參考“費曼學習法”,就是把這篇文章學到的理論,通過演講的方式表達出來,可以和同事,或者自己自問自答,
關注[跟著Mic學架構]公眾號,獲取更多精品原創

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/341673.html
標籤:Java
上一篇:結構體位元組對齊和共用體大小
