重寫(OverWrite)和過載(OverLoad)及各發生在哪個時期(編譯期or執行期)
重寫(OverWrite):
重寫是子類對父類的允許訪問的方法的實現過程進行重新編寫, 返回值和形參都不能改變。即外殼不變,核心重寫!
重寫的好處在於子類可以根據需要,定義特定於自己的行為。 也就是說子類能夠根據需要實現父類的方法。
重寫方法不能丟擲新的檢查異常或者比被重寫方法申明更加寬泛的異常。例如: 父類的一個方法申明瞭一個檢查異常 IOException,但是在重寫這個方法的時候不能丟擲 Exception 異常,因為 Exception 是 IOException 的父類,只能丟擲 IOException 的子類異常。
在面向物件原則裡,重寫意味著可以重寫任何現有方法。
方法的重寫規則
* 引數列表必須完全與被重寫方法的相同;
* 返回型別必須完全與被重寫方法的返回型別相同;
* 訪問許可權不能比父類中被重寫的方法的訪問許可權更低。例如:如果父類的一個方法被宣告為public,那麼在子類中重寫該方法就不能宣告為protected。
* 父類的成員方法只能被它的子類重寫。
* 宣告為final的方法不能被重寫。
* 宣告為static的方法不能被重寫,但是能夠被再次宣告。
* 子類和父類在同一個包中,那麼子類可以重寫父類所有方法,除了宣告為private和final的方法。
* 子類和父類不在同一個包中,那麼子類只能夠重寫父類的宣告為public和protected的非final方法。
* 重寫的方法能夠丟擲任何非強制異常,無論被重寫的方法是否丟擲異常。但是,重寫的方法不能丟擲新的強制性異常,或者比被重寫方法宣告的更廣泛的強制性異常,反之則可以。
* 構造方法不能被重寫。
* 如果不能繼承一個方法,則不能重寫這個方法。
方法的重寫發生在執行時。因為在編譯時,編譯器是無法知道我們到底是呼叫父類的方法還是子類的方法,相反的,只有在實際執行的時候,我們才知道應該呼叫哪個方法。這個也是java執行時多型的體現。
過載(OverLoad):
對於類的方法(包括從父類中繼承的方法),如果有兩個方法的方法名相同,但引數不一樣,則一個方法是另一個方法的過載方法。
過載方法必須滿足以下條件:
(1)方法名相同;
(2)方法的引數型別、個數、順序至少有一項不同;
(3)方法的返回值型別可以不同;
(4)方法的修飾符可以不同;
在一個類中不允許定義兩個方法名相同,並且引數簽名也完全相同的方法。因為假如存在這樣的方法,Java虛擬機器在執行時就無法決定到底執行哪個方法,引數簽名是指引數的型別、個數和順序。
注意:過載方法跟其返回型別沒有關係。
方法的過載發生在編譯時。在編譯過程中,編譯器必須根據引數型別以及長度來確定到底是呼叫的哪個方法,這個也是Java編譯時多型的體現。
import java.math.BigDecimal;
import java.util.*;
/**
* Created by ZhangAnmy on 18/9/23.
*/
public class OverLoadTest {
public static String classify(Set<?> set){
return "set";
}
public static String classify(List<?> list){
return "list";
}
public static String classify(Collection<?> col){
return "collection";
}
public static void main(String[] args)
{
Collection<?>[] collection={
new HashSet<String>(),
new ArrayList<BigDecimal>(),
new HashMap<String,String>().values()
};
for(Collection<?> collection1 : collection)
{
System.out.println(classify(collection1));
}
}
}
執行結果:
collection
collection
collection
classify方法被過載了,而要呼叫哪個過載方法是在編譯時做出決定的。對於for迴圈的三次迭代,引數編譯時型別都是相同的:Collection<?>,每次迭代的執行時期的型別是不相同的,但這個不影響過載方法的選擇。因為該引數的編譯時型別為Collection<?>,所以,唯一合適的過載方法是第三個classify(Collection<?>),在迴圈的每次迭代中,都會呼叫這個過載方法。
與多型的父類引用指向子類物件,編譯時看父類,執行時看子類不同的是,在過載的方法選擇過程中,物件的執行時型別並不影響哪個過載版本被執行,選擇工作是在編譯時期完成的,完全基於引數的編譯時期的型別。 但是多型的父類引用指向子類物件與這個不一樣。舉例如下:
public class Fu {
String name(){
return "fu”;
}
}
public class Zi extends Fu {
String name(){
return "zi”;
}
}
public class Sun extends Zi {
String name(){
return "Sun";
}
}
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(new Fu());
list.add(new Zi());
list.add(new Sun());
for (Fu item : list)
{
System.out.println(item.name());
}
}
}
執行結果:
fu
zi
sun
儘管每次迴圈迭代過程中編譯時期的型別都是Fu,執行時是各自的型別,當呼叫被覆蓋的方法時,物件編譯時期的型別不影響哪個方法被執行。