過載與重寫
| 導航 類和物件 主題: |
在一個類中,可以有幾個同名方法。但是它們必須有不同的簽名。方法的簽名由其名稱、引數型別及其引數的順序組成。方法的簽名不包含其返回型別、可見性或它可能丟擲的異常。在同一個類中定義兩個或多個共享相同名稱但具有不同引數的方法的做法稱為方法過載。
在一個類中具有相同名稱的方法被稱為過載方法。過載方法對 JVM 沒有特殊的好處,但對程式設計師來說,讓幾個方法執行相同的事情但使用不同的引數非常有用。例如,我們可能有操作runAroundThe表示為兩個同名但輸入引數型別不同的方法
程式碼部分 4.22:方法過載。
public void runAroundThe(Building block) {
...
}
public void runAroundThe(Park park) {
...
}
|
一種型別可以是另一種型別的子類
|
|
雖然兩種方法都適合呼叫帶有String引數的方法,但將呼叫引數型別最接近的方法。更準確地說,它將呼叫引數型別是另一個方法的引數型別的子類的方法。因此,aObject將輸出Object。注意!引數型別由物件的宣告型別定義,不是其例項化型別!
以下兩個方法定義是有效的
程式碼部分 4.23:帶型別順序的方法過載。
public void logIt(String param, Error err) {
...
}
public void logIt(Error err, String param) {
...
}
|
因為型別順序不同。如果兩個輸入引數都是 String 型別,那就會有問題,因為編譯器將無法區分兩者
程式碼部分 4.24:錯誤的方法過載。
public void logIt(String param, String err) {
...
}
public void logIt(String err, String param) {
...
}
|
編譯器也會對以下方法定義給出錯誤
程式碼部分 4.25:另一種錯誤的方法過載。
public void logIt(String param) {
...
}
public String logIt(String param) {
String retValue;
...
return retValue;
}
|
請注意,返回型別不是唯一簽名的部分。為什麼不呢?原因是可以在不將方法的返回值賦值給變數的情況下呼叫方法。此功能來自 C 和 C++。因此,對於呼叫
程式碼部分 4.26:不明確的方法呼叫。
logIt(msg);
|
編譯器將不知道呼叫哪個方法。對於丟擲的異常也是如此。
問題 4.6:Question6類的哪些方法會導致編譯錯誤?
Question6.java
public class Question6 {
public void example1() {
}
public int example1() {
}
public void example2(int x) {
}
public void example2(int y) {
}
private void example3() {
}
public void example3() {
}
public String example4(int x) {
return null;
}
public String example4() {
return null;
}
}
|
Question6.java
public class Question6 {
public void example1() {
}
public int example1() {
}
public void example2(int x) {
}
public void example2(int y) {
}
private void example3() {
}
public void example3() {
}
public String example4(int x) {
return null;
}
public String example4() {
return null;
}
}
|
example1、example2 和 example3 方法會導致編譯錯誤。example1 方法不能共存,因為它們具有相同的簽名(記住,返回型別不是簽名的一部分)。example2 方法不能共存,因為引數的名稱不是簽名的一部分。example3 方法不能共存,因為方法的可見性不是簽名的一部分。example4 方法可以共存,因為它們具有不同的方法簽名。
您不必過載,也可以使用動態數量的引數。在最後一個引數之後,您可以傳遞相同型別的可選無限引數。這些引數透過新增最後一個引數並在其型別後新增...來定義。動態引數將作為陣列接收
程式碼部分 4.27:可變引數。
public void registerPersonInAgenda(String firstName, String lastName, Date... meeting) {
String[] person = {firstName, lastName};
lastPosition = lastPosition + 1;
contactArray[lastPosition] = person;
if (meeting.length > 0) {
Date[] temporaryMeetings = new Date[registeredMeetings.length + meeting.length];
for (i = 0; i < registeredMeetings.length; i++) {
temporaryMeetings[i] = registeredMeetings[i];
}
for (i = 0; i < meeting.length; i++) {
temporaryMeetings[registeredMeetings.length + i] = meeting[i];
}
registeredMeetings = temporaryMeetings;
}
}
|
上面的方法可以用動態數量的引數呼叫,例如
程式碼部分 4.27:建構函式呼叫。
registerPersonInAgenda("John", "Doe");
registerPersonInAgenda("Mark", "Lee", new Date(), new Date());
|
此功能在 Java 1.5 之前不可用。
建構函式可以過載。您可以定義多個具有不同引數的建構函式。例如
程式碼清單 4.12:建構函式。
public class MyClass {
private String memberField;
/**
* MyClass Constructor, there is no input parameter
*/
public MyClass() {
...
}
/**
* MyClass Constructor, there is one input parameter
*/
public MyClass(String param1) {
memberField = param1;
...
}
}
|
在程式碼清單 4.12中,我們定義了兩個建構函式,一個沒有輸入引數,另一個有一個輸入引數。您可能會問將呼叫哪個建構函式。這取決於使用new關鍵字建立物件的方式。見下文
程式碼部分 4.29:建構函式呼叫。
// The constructor with no input parameter will be called
MyClass obj1 = new MyClass();
// The constructor with one input param. will be called
MyClass obj2 = new MyClass("Init Value");
|
在程式碼部分 4.29中,我們從同一個類建立了兩個物件,或者也可以說obj1和obj2都具有相同的型別。這兩者的區別在於,在第一個中,memberField欄位未初始化,在第二個中,它被初始化為"Init Value"。建構函式也可以從另一個建構函式呼叫,見下文
程式碼清單 4.13:建構函式池。
public class MyClass {
private String memberField;
/**
* MyClass Constructor, there is no input parameter
*/
public MyClass() {
MyClass("Default Value");
}
/**
* MyClass Constructor, there is one input parameter
*/
public MyClass(String param1) {
memberField = param1;
...
}
}
|
在程式碼清單 4.13中,沒有輸入引數的建構函式呼叫了具有預設初始值的另一個建構函式。此呼叫必須是建構函式的第一條指令,否則會發生編譯錯誤。該程式碼為使用者提供了選擇,可以選擇使用預設值建立物件,或者使用指定的值建立物件。第一個建構函式也可以使用this關鍵字編寫
程式碼部分 4.30:另一種建構函式池。
public MyClass() {
this("Default Value");
}
|
這樣的呼叫減少了程式碼重複。
為了便於記住在方法重寫中可以做什麼,請記住,在類物件中可以做的所有操作,在子類物件中也可以做,只是行為可能會改變。子類應該是協變的。
雖然方法簽名在類內部必須是唯一的,但可以在不同的類中定義相同的簽名。如果我們定義了一個在超類中存在的方法,那麼我們就重寫了超類方法。這被稱為方法重寫。這與方法過載不同。方法過載是指名稱相同但簽名不同的方法。方法重寫是指在繼承類之間名稱和簽名相同的方法。
返回值型別會導致我們上面看到的問題。當我們重寫超類方法時,返回值型別也必須相同。如果不相同,編譯器會報錯。
注意!如果一個類聲明瞭兩個具有相同名稱的公共方法,而一個子類重寫了其中一個方法,則子類仍然會繼承另一個方法。在這方面,Java程式語言與C++不同。
方法重寫與動態連結或執行時繫結有關。為了使方法重寫起作用,要呼叫的方法呼叫不能在編譯時確定。它將在執行時決定,並在表中查詢。
程式碼部分 4.31:執行時繫結。
MyClass obj;
if (new java.util.Calendar().get(java.util.Calendar.AM_PM) == java.util.Calendar.AM) {
// Executed during a morning
obj = new SubOfMyClass();
} else {
// Executed during an afternoon
obj = new MyClass();
}
obj.myMethod();
|
在程式碼部分 4.31中,如果在早上執行,第 3 行的表示式為真,如果在下午執行,則為假。因此,obj的例項將是MyClass或SubOfMyClass,具體取決於執行時間。因此,在編譯時無法確定方法地址。因為obj引用可以指向一個物件及其所有子物件,並且只有在執行時才會知道,所以會維護一個包含所有可能方法地址的表以供呼叫。不要混淆
程式碼部分 4.32:宣告型別和例項化型別。
obj.myMethod(myParameter);
|
使用被呼叫物件的例項化型別(obj)和引數物件的宣告型別(myParameter)來搜尋此方法的實現。
還有另一個規則是,當你進行重寫時,重寫超類方法的新方法的可見性不能降低。但是,可見性可以提高。因此,如果超類方法的可見性是public,則重寫方法不能是package或private。重寫方法必須丟擲與超類相同的異常,或其子異常。
super引用父類(即 super.someMethod())。它可以在子類中使用來訪問子類已重寫的繼承方法或子類已隱藏的繼承欄位。
| 一個常見的錯誤是認為如果我們可以重寫方法,我們也可以重寫成員變數。事實並非如此,因為這樣做毫無意義。你不能重新定義超類中私有的變數,因為這樣的變數是不可見的。 |
