重載(Overload)和重寫(Override)的區別。重載的方法能否根據返回類型進行區分
- 2021 年 10 月 29 日
- 筆記
面試題: 重載(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學架構]公眾號,獲取更多精品原創