- Overloading=多載。
- Overriding=覆載。
就在這個時候,國內的大學生發現這可能是教授為了能當更多人而玩的文字遊戲,又或者是當初的翻譯者害怕太多人學會之後飯碗不保,刻意翻得很奇怪,於是提出更明確的翻譯方式以自救:
給予太多工作,顧名思義就是給同一個人太大的工作量,讓他什麼事情都要包辦。在物件導向程式設計的世界裡,就是給予一個方法(method)或一個運算子(operator)多重任務的意思!最常見的例子就是Java中的加號運算子(+),可用在數字相加也可用在字串串接,相當於一個人揹了兩項任務。如下列程式碼,給予+號不同型態的運算元,就會進行不同的操作。
Code 1.
int i = 1+2+3; // i = 6 String str = "Hello"+" World"; // str = "Hello World" |
Java目前並不開放給程式員Overload運算子,只開放方法的Overloading。要在Java中Overload方法,只要使用同樣的名字與不同的參數形式宣告多個方法就行了,如下:
Code 2.
class Overloading{ String hello(String str){ return "Hello!"; } int hello(int i){ return 100; } String hello(String str1, String str2){ return str1+str2; } public static void main(String... arg){ Overloading over = new Overloading(); System.out.println(over.hello("Go!")); System.out.println(over.hello("Hello"," World")); System.out.println(over.hello(300)); } } |
執行結果 |
Hello! Hello World 100 |
hello方法有三種宣告,分別接受不同型態與數量的參數。從呼叫方法的程式碼來看,像是我們讓hello方法處理三種不同的參數形式。然而實際上,這三個方法雖然有同樣的名稱,卻可以視為不同的方法。
原則上只要方法名稱一樣但方法簽名(Method Signature,即方法名稱加上參數形式)不同,就是合法的Overloading,回傳值型態則無所謂,呼叫方法的時候不要弄錯即可。實際上Java編譯器在編譯之後確實是產生3個不同的方法宣告。你可能會好奇這樣為什麼需要Overloading來擾亂視聽,其實沒有Overloading才真的很不方便,想像一下要替幾個功能一致只是參數形式不同的方法取名字的時候,光是要想出清楚卻又不能重複的名字,就讓人感到很挫折了。
忤逆家長,顧名思義就是不理會家長的那一套,決心貫徹自己的做法!在物件導向設計的世界中,指的就是重新定義從父類別那繼承下來的方法。Overriding的概念比較難以文字解釋,因此話不多說,直接切入程式碼。
我們現在有一個父類別如下:
Code 3.
class Parent{ protected Money work(){ System.out.println("家長種田"); return new Money(300); } } |
代表家長種田,每次賺300塊。Money類別內容如下:
Code 4.
class Money{ private double money; public Money(double amount){ this.money = amount; } public double getNTAmount(){ return money; } } |
現在這個家長生了個有志氣的孩子。這個孩子不想繼承家業,憑自己努力用功讀書,最終成為一個優秀的Java程式員。這個孩子雖然也要工作(work),卻不去繼承家長的工作方式,自己定義了work的實作,如下:
Code 5.
class Child extends Parent{ public Money work(){ System.out.println("兒子寫Java"); return new Money(30); }
} |
像這樣改寫從父類別繼承下來的方法,就是忤逆家長,就是Overriding!
Overriding的限制比較多,除了方法名稱必須要與「被忤逆」的方法一樣之外,方法的回傳值型態與參數形式也必須與父類別一模一樣(否則就變成Overloading)!另外,方法的存取權限只能和父類別的方法一樣或更開放。也就是說,若父類別方法是使用protected權限,子類別想要「忤逆」該方法的話,權限就只能是protected或public。還有一點得注意:Final的方法不予許「被忤逆」。
值得一提的是,Java 5 開始有一種例外情況予許子類別「忤逆」的方法的傳回值型態可以是「被忤逆」的方法的回傳值型態的子類別。簡單的說,Child類別的work方法的回傳值型態不一定要是Money類別,也可以是Money的子類別。這就叫共變回傳(Covarient Return)。
舉例來說,假如那個孩子成為Java程式員之後,因為勤奮工作,被主管推薦到矽谷的總公司上班,薪水也從台幣換成美金,我們就定義一個美金(USDollar)類別繼承Money類別:
Code 6.
class USDollar extends Money{ public USDollar(double amount){ super(amount); } public double getNTAmount(){ return super.getNTAmount()*32; } } |
並且將Child中work方法的回傳值型態改為USDollar:
Code 7.
class Child extends Parent{
public USDollar work(){
System.out.println("兒子寫Java"); return new USDollar(30); } } |
由於USDollar是Money的子類別,因此在Java 5之後能夠作為「忤逆」方法的合法回傳值型態。
關於共變回傳,還是不太熟悉的人可以參考這篇文章 http://tw.knowledge.yahoo.com/question/question?qid=1508092008302
也許有人會覺得要記那麼多原則很煩,但其實萬變不離其宗,大原則就是子類別必須能被當作父類別使用(如Code 8)。這就是著名的Liskov代換原則(LSP)。假設子類別的「忤逆」方法的存取權限、參數形式或傳回值型態與父類別「被忤逆」的方法不相容,如何確保子類別能被當作父類型型態使用呢?這也就是為何「忤逆」方法的存取權限必須與「被忤逆」的方法一樣或更開放,回傳值型態也必須要能相容的原因。
Code 8.
class Main{ public static void main(String... arg){ Parent p = new Child(); //子類別實體當父類別用 Money money = p.work(); System.out.println("薪水:"+money.getNTAmount()); } } |
執行結果 |
兒子寫Java 薪水:960.0 |
在像Java或C++這種靜態語言中使用Overloading與Overriding會有一定的風險在,因為兩者很容易混淆,導致執行結果可能不是程式員所預期的。例如下列程式碼:
Code 9.
import java.util.*; class Problem{ public static void main(String... arg){ List ‹String› list = new LinkedList‹ String › (); list.add("Hello"); list.add("World"); list.remove(0); ((Collection)list).remove(0); System.out.println("length"+ list.size()); } } |
猜猜最後list中會有多少個元素?
答案是:1個。
你一定會疑惑增加兩個元素後又刪除兩個元素,為什麼結果不是個空串列。這就要看看List介面的remove方法是Overloading還是Overriding了。Collection介面固然提供了一個remove方法:
Collection
boolean remove(Object o) |
然而List介面不只繼承了Collection的remove,還Overload了另一個remove方法:
List
boolean remove(Object o) E remove(int index) |
因此當list.remove(0)被呼叫時,實際上是呼叫到List介面Overload的另一個remove方法(因為參數型態最接近),而不是定義在Collection中的remove方法。然而,當list被轉型為Collection型態並呼叫remove方法時,由於Collection介面並沒有定義接收int型態參數的remove方法,因此參數0會被Auto-boxing成Integer型態進而與Object型態相容(所有型態都間接或直接繼承Object類別),所以Collection介面的remove方法還是會被呼叫。但由於兩個remove方法行為不同(一個是移除Collection中的o元素,一個是移除List中第index個元素),導致結果不是我們所預期的空串列。這種情況下,若沒有去查JavaDoc看看Collection與List的差別,會以為List介面的remove方法是Overriding而不是Overloading,進而完全找不到問題出在哪裡,因此在使用上必須注意這點。
如果擔心在Overriding時因為參數型態打錯而變成Overloading,可以在方法前加上@Override這樣的Annotation。@Override會通知編譯器在編譯時確認該方法是否真的有Overriding。以上面的Child類別為例:
List
class Child extends Parent{ //打錯參數形式,造成Overloading @Override public USDollar work(String str){ System.out.println("兒子寫Java"); return new USDollar(30); } } |
編譯訊息 |
method does not override or implement a method from a supertype @Override ^ 1 error |
萬一打錯字變成Overloading就無法編譯,可以節省很多因為打錯字而浪費掉的debug的時間!
寫的超棒
回覆刪除謝謝
刪除讚!
回覆刪除寫得非常容易懂!!
回覆刪除超棒!!!
回覆刪除非常清楚,用淺顯易懂的例子(如忤逆跟工作量多舉例),讓我更加認識多載與覆載的差異了,感謝大大
回覆刪除