有時候,類的同一種功能有多種實現方式,到底採用哪一種實現方式,取決於調用者給定的參數。例如雜技師能訓練動物,對於不一樣的動物有不一樣的訓練方式。java
public void train(Dog dog){編程
//訓練小狗站立、排隊、作算術jvm
…ide
}函數
public void train(Monkey monkey){spa
//訓練小猴敬禮、翻筋斗、騎自行車對象
…繼承
}接口
再例如某個類的一個功能是比較兩個城市是否相同,一種方式是按兩個城市的名字進行比較,另外一種方式是按兩個城市的名字,以及城市所在國家的名字進行比較。ip
public boolean isSameCity (String city1,String city2){
return city1.equals(city2);
}
public boolean isSameCity(String city1,String city2,String country1,String country2){
return isSameCity(city1, city2) && country1.equals(country2);
}
再例如java.lang.Math類的max()方法可以從兩個數字中取出最大值,它有多種實現方式。
public static int max(int a,int b)
public static int max(long a,long b)
public static int max(float a,float b)
public static int max(double a,double b)
如下程序屢次調用Math類的max()方法,運行時,Java虛擬機先判斷給定參數的類型,而後決定到底執行哪一個max()方法。
//參數均爲int類型,所以執行max(int a,int b)方法
Math.max(1,2);
//參數均爲float類型,所以執行max(float a,float b)方法
Math.max(1.0F, 2.0F);
//參數中有一個是double類型,自動把另外一個參數2轉換爲double類型,
//執行max(double a,double b)方法
Math.max(1.0,2);
對於類的方法(包括從父類中繼承的方法),若是有兩個方法的方法名相同,但參數不一致,那麼能夠說,一個方法是另外一個方法的重載方法。
重載方法必須知足如下條件:
l 方法名相同。
l 方法的參數類型、個數、順序至少有一項不相同。
l 方法的返回類型能夠不相同。
l 方法的修飾符能夠不相同。
在一個類中不容許定義兩個方法名相同,而且參數簽名也徹底相同的方法。由於假如存在這樣的兩個方法,Java虛擬機在運行時就沒法決定到底執行哪一個方法。參數簽名是指參數的類型、個數和順序。
例如如下Sample類中已經定義了一個amethod()方法。
public class Sample{
public void amethod(int i, String s){}
//加入其餘方法
}
下面哪些方法能夠加入到Sample類中,而且保證編譯正確呢?
A)public void amethod(String s, int i){} (能夠)
B)public int amethod(int i, String s){return 0;} (不能夠)
C)private void amethod(int i, String mystring){} (不能夠)
D)public void Amethod(int i, String s) {} (能夠)
E)abstract void amethod(int i); (不能夠)
選項A的amethod()方法的參數順序和已有的不同,因此能做爲重載方法加入到Sample類中。
選項B和選項C的amethod()方法的參數簽名和已有的同樣,因此不能加入到Sample類中。對於選項C,儘管String類型的參數的名字和已有的不同,但比較參數簽名無須考慮參數的具體名字。
選項D的方法名爲Amethod,與已有的不同,因此能加入到Sample類中。
選項E的方法的參數數目和已有的不同,所以是一種重載方法。但因爲此處的Sample類不是抽象類,因此不能包含這個抽象方法。假如把Sample類改成抽象類,就能把這個方法加入到Sample類中了。
再例如,如下Sample類中已經定義了一個做爲程序入口的main()方法。
abstract public class Sample{
public static void main( String[] s){}
//加入其餘方法
}
下面哪些方法能夠加入到Sample類中,而且保證編譯正確呢?
A)abstract public void main(String s, int i); (能夠)
B)public final static int main( String[] s){} (不能夠)
C)private void main(int i, String mystring){} (能夠)
D)public void main( String s) throws Exception{} (能夠)
做爲程序入口的main()方法也能夠被重載。以上選項A、C和D均可以被加入到Sample類中。選項B與已有的main()方法有相同的方法簽名,所以不容許再加入到Sample類中。
方法覆蓋
假若有100個類,分別爲Sub1,Sub2…Sub100,它們的一個共同行爲是寫字,除了Sub1類用腳寫字 外,其他的類都用手寫字。能夠抽象出一個父類Base,它有一個表示寫字的方法write(),那麼這個方法到底如何實現呢?從儘量提升代碼可重用性的 角度看,write()方法應該採用適用於大多數子類的實現方式,這樣就能夠避免在大多數子類中重複定義write()方法。所以Base類的write ()方法的定義以下:
public void write(){ //Base類的write()方法
//用手寫字
…
}
因爲Sub1類的寫字的實現方式與Base類不同,所以在Sub1類中必須從新定義write()方法。
public void write(){ //Sub1類的write()方法
//用腳寫字
…
}
若是在子類中定義的一個方法,其名稱、返回類型及參數簽名正好與父類中某個方法的名稱、返回類型及參數簽名相匹配,那麼能夠說,子類的方法覆蓋了父類的方法。
覆蓋方法必須知足多種約束,下面分別介紹。
(1)子類方法的名稱、參數簽名和返回類型必須與父類方法的名稱、參數簽名和返回類型一致。例如如下代碼將致使編譯錯誤。
public class Base {
public void method() {…}
}
public class Sub extends Base{
public int method() { //編譯錯誤,返回類型不一致
return 0;
}
}
Java編譯器首先判斷Sub類的method()方法與Base類的method()方法的參數簽名,因爲二者一 致,所以Java編譯器認爲Sub類的method()方法試圖覆蓋父類的方法,既然如此,Sub類的method()方法就必須和被覆蓋的方法具備相同 的返回類型。
如下代碼中子類覆蓋了父類的一個方法,而後又定義了一個重載方法,這是合法的。
public class Base {
public void method() {…}
}
public class Sub extends Base {
public void method(){…} //覆蓋Base類的method()方法
public int method(int a) { //重載method()方法
return 0;
}
}
(2)子類方法不能縮小父類方法的訪問權限。例如如下代碼中子類的method()方法是私有的,父類的method()方法是公共的,子類縮小了父類方法的訪問權限,這是無效的方法覆蓋,將致使編譯錯誤。
public class Base {
public void method() {…}
}
public class Sub extends Base {
private void method() {…} //編譯錯誤,子類方法縮小了父類方法的訪問權限
}
爲何子類方法不容許縮小父類方法的訪問權限呢?這是由於假如沒有這個限制,將會與Java語言的多態機制發生衝突。例如對於如下代碼:
Base base=new Sub(); //base變量被定義爲Base類型,但引用Sub類的實例
base.method();
Java編譯器認爲以上是合法的代碼。但在運行時,根據動態綁定規則,Java虛擬機會調用base變量所引用的 Sub實例的method()方法,若是這個方法爲private類型,Java虛擬機就沒法訪問它。因此爲了不這樣的矛盾,Java語言不容許子類方 法縮小父類中被覆蓋方法的訪問權限。本章第6.6節(多態)對多態作了進一步的闡述。
(3)子類方法不能拋出比父類方法更多的異常,關於異常的概念參見第9章(異常處理)。子類方法拋出的異常必須和父類方法拋出的異常相同,或者子類方法拋出的異常類是父類方法拋出的異常類的子類。
例如,假設異常類ExceptionSub1和ExceptionSub2是ExceptionBase類的子類,則如下的代碼是合法的:
public class Base {
void method()throws ExceptionBase{}
}
public class Sub1 extends Base {
void method()throws ExceptionSub1{}
}
public class Sub2 extends Base {
void method()throws ExceptionSub1,ExceptionSub2{}
}
public class Sub3 extends Base {
void method()throws ExceptionBase{}
}
如下代碼不合法:
public class Base {
void method() throws ExceptionSub1{ }
}
public class Sub1 extends Base {
void method()throws ExceptionBase {} //編譯出錯
}
public class Sub2 extends Base {
void method()throws ExceptionSub1,ExceptionSub2 {} //編譯出錯
}
爲何子類方法不容許拋出比父類方法更多的異常呢?這是由於假如沒有這個限制,將會與Java語言的多態機制發生衝突。例如對於如下代碼:
Base base=new Sub2(); //base變量被定義爲Base類型,但引用Sub2類的實例
try{
base.method();
}catch(ExceptionSub1 e){ … } //僅僅捕獲ExceptionSub1異常
Java編譯器認爲以上是合法的代碼。但在運行時,根據動態綁定規則,Java虛擬機會調用base變量所引用的 Sub2實例的method()方法。假如Sub2實例的method()方法拋出ExceptionSub2異常,因爲該異常沒有被捕獲,將致使程序異 常終止。
(4)方法覆蓋只存在於子類和父類(包括直接父類和間接父類)之間。在同一個類中方法只能被重載,不能被覆蓋。
(5)父類的靜態方法不能被子類覆蓋爲非靜態方法。例如如下的代碼將致使編譯錯誤:
public class Base {
public static void method() { }
}
public class Sub extends Base {
public void method() { } //編譯出錯
}
(6)子類能夠定義與父類的靜態方法同名的靜態方法,以便在子類中隱藏父類的靜態方法。在編譯時,子類定義的靜態方法也必須知足與方法覆蓋相似的約束:方法的參數簽名一致,返回類型一致,不能縮小父類方法的訪問權限,不能拋出更多的異常。例如如下代碼是合法的:
public class Base {
static int method(int a) throws BaseException{ return 0; }
}
public class Sub extends Base{
public static int method(int a) throws SubException { return 0; }
}
子類隱藏父類的靜態方法和子類覆蓋父類的實例方法,這二者的區別在於:運行時,Java虛擬機把靜態方法和所屬的類 綁定,而把實例方法和所屬的實例綁定。下面舉例來解釋這一區別。在例程6-1中,Base類和它的子類即Sub類中都定義了實例方法method()和靜 態方法staticMethod()。
例程6-1 Sub.java
package hidestatic;
class Base{
void method(){ //實例方法
System.out.println("method of Base");
}
static void staticMethod(){ //靜態方法
System.out.println("static method of Base");
}
}
public class Sub extends Base{
void method(){ //覆蓋父類的實例方法method()
System.out.println("method of Sub");
}
static void staticMethod(){ //隱藏父類的靜態方法staticMethod()
System.out.println("static method of Sub");
}
public static void main(String args[]){
Base sub1=new Sub(); //sub1變量被聲明爲Base類型,引用Sub實例
sub1.method(); //打印 method of Sub
sub1.staticMethod(); //打印 static method of Base
Sub sub2=new Sub(); //sub2變量被聲明爲Sub類型,引用Sub實例
sub2.method(); //打印 method of Sub
sub2.staticMethod(); //打印 static method of Sub
}
}
運行Sub類的main()方法,程序將輸出:
method of Sub
static method of Base
method of Sub
static method of Sub
引用變量sub1和sub2都引用Sub類的實例,Java虛擬機在執行sub1.method()和sub2.method()時,都調用Sub實例的method()方法,此時父類Base的實例方法method()被子類覆蓋。
引用變量sub1被聲明爲Base類型,Java虛擬機在執行sub1. staticMethod()時,調用Base類的staticMethod()方法,可見父類Base的靜態方法staticMehtod()不能被子類覆蓋。
引用變量sub2被聲明爲Sub類型,Java虛擬機在執行sub2. staticMethod()時,調用Sub類的staticMethod()方法,Base類的staticMehtod()方法被Sub類的staticMehtod()方法隱藏。
(7)父類的非靜態方法不能被子類覆蓋爲靜態方法。例如如下代碼是不合法的:
public class Base {
void method() { }
}
public class Sub extends Base {
static void method() { } //編譯出錯
}
(8)父類的私有方法不能被子類覆蓋。例如在例程6-2中,子類Sub中定義了一個和父類Base中的方法同名、參 數簽名和返回類型一致,但訪問權限不一致的方法showMe(),父類中showMe()的訪問權限爲private,而子類中showMe()的訪問權 限爲public。儘管這在形式上和覆蓋很類似,但Java虛擬機對此有不一樣的處理機制。子類方法覆蓋父類方法的前提是,子類必須能繼承父類的特定方法, 因爲Base類的private類型的showMe()方法不能被Sub類繼承,所以Base類的showMe()方法和Sub類的showMe()方法 之間並無覆蓋關係。
例程6-2 Sub.java
package privatetest;
class Base {
private String showMe() {
return "Base";
}
public void print(){
System.out.println(showMe()); //到底調用Base類的showMe()仍是Sub類的showMe()?
}
}
public class Sub extends Base {
public String showMe(){
return "Sub";
}
public static void main(String args[]){
Sub sub=new Sub();
sub.print();
}
}
執行以上Sub類的main()方法,會打印出結果「Base」,這是由於print()方法在Base類中定義,所以print()方法會調用在Base類中定義的private類型的showMe()方法。
可是若是把Base類的showMe()方法改成public類型,其餘代碼不變:
public class Base {
public String showMe() {
return "Base";
}
…
}
再執行以上Sub類的main()方法的代碼,會打印出結果「Sub」,這是由於此時Sub類的showMe()方 法覆蓋了Base類的showMe()方法。所以儘管print()方法在Base類中定義,Java虛擬機仍是會調用當前Sub實例的showMe() 方法。
(9)父類的抽象方法能夠被子類經過兩種途徑覆蓋:一是子類實現父類的抽象方法;二是子類從新聲明父類的抽象方法。例如如下代碼合法:
public abstract class Base {
abstract void method1();
abstract void method2();
}
public abstract class Sub extends Base {
public void method1(){…} //實現method1()方法,而且擴大訪問權限
public abstract void method2(); //從新聲明method2()方法,僅僅擴大訪問權限,但不實現
Tips
狹義的理解,覆蓋僅指子類覆蓋父類的具體方法,即非抽象方法,在父類中提供了方法的默認實現方式,而子類採用不一樣的實現方式。在本書中,爲了敘述方便,把子類實現父類的抽象方法也看作方法覆蓋。例如如下代碼不合法:
public abstract class Base {
abstract void method1();
abstract void method2();
}
public abstract class Sub extends Base {
private void method1(){…} //編譯出錯,不能縮小訪問權限
private abstract void method2(); //編譯出錯,不能縮小訪問權限
}
(10)父類的非抽象方法能夠被覆蓋爲抽象方法。例如如下代碼合法:
public class Base {
void method(){ }
}
public abstract class Sub extends Base {
public abstract void method(); //合法
}
圖6-2 Sub類繼承Base類
在本書提供的UML類框圖中,在子類中只會顯示子類特有的方法及覆蓋父類的方法,而不會顯示直接從父類中繼承的方 法。例如,圖6-2代表Base類是抽象類(Base名字用斜體字表示),method1()爲抽象方法(method1名字用斜體字表示), method2()和method3()爲具體方法。Sub類是Base類的子類,Sub類實現了Base類的method1()方法,覆蓋了Base類 的method2()方法,直接繼承Base類的method3()方法,此外Sub類還有本身的method4()方法。