Java語法總結 - 方法

          Posted on 2007-10-30 18:49 Raylong 閱讀(6441) 評論(8)  編輯  收藏 所屬分類: Java語法總結
          Java語法總結 - 方法

          一、方法的重寫。

          1、重寫只能出現在繼承關系之中。當一個類繼承它的父類方法時,都有機會重寫該父類的方法。一個特例是父類的方法被標識為final。重寫的主要優點是能夠定義某個子類型特有的行為。
              class Animal {
                  public void eat(){
                      System.out.println ("Animal is eating.");
                  }
              }
              
              class Horse extends Animal{
                  public void eat(){
                      System.out.println ("Horse is eating.");
                  }
              }

          2、對于從父類繼承來的抽象方法,要么在子類用重寫的方式設計該方法,要么把子類也標識為抽象的。所以抽象方法可以說是必須要被重寫的方法。

          3、重寫的意義。
          重寫方法可以實現多態,用父類的引用來操縱子類對象,但是在實際運行中對象將運行其自己特有的方法。
              public class Test {
                  public static void main (String[] args) {
                      Animal h = new Horse();
                      h.eat();    
                  }
              }

              class Animal {
                  public void eat(){
                      System.out.println ("Animal is eating.");
                  }
              }
              
              class Horse extends Animal{
                  public void eat(){
                      System.out.println ("Horse is eating.");
                  }
                  public void buck(){
                  }
              }

          一個原則是:使用了什么引用,編譯器就會只調用引用類所擁有的方法。如果調用子類特有的方法,如上例的h.buck(); 編譯器會抱怨的。也就是說,編譯器只看引用類型,而不是對象類型。

          4、重寫方法的規則。
          若想實現一個合格重寫方法,而不是重載,那么必須同時滿足下面的要求!

          A、重寫規則之一:重寫方法不能比被重寫方法限制有更嚴格的訪問級別。
          (但是可以更廣泛,比如父類方法是包訪問權限,子類的重寫方法是public訪問權限。)
          比如:Object類有個toString()方法,開始重寫這個方法的時候我們總容易忘記public修飾符,編譯器當然不會放過任何教訓我們的機會。出錯的原因就是:沒有加任何訪問修飾符的方法具有包訪問權限,包訪問權限比public當然要嚴格了,所以編譯器會報錯的。

          B、重寫規則之二:參數列表必須與被重寫方法的相同。
          重寫有個孿生的弟弟叫重載,也就是后面要出場的。如果子類方法的參數與父類對應的方法不同,那么就是你認錯人了,那是重載,不是重寫。

          C、重寫規則之三:返回類型必須與被重寫方法的返回類型相同。
          父類方法A:void eat(){}  子類方法B:int eat(){}  兩者雖然參數相同,可是返回類型不同,所以不是重寫。
          父類方法A:int eat(){}   子類方法B:long eat(){}  返回類型雖然兼容父類,但是不同就是不同,所以不是重寫。

          D、重寫規則之四:重寫方法不能拋出新的異常或者比被重寫方法聲明的檢查異常更廣的檢查異常。但是可以拋出更少,更有限或者不拋出異常。
              import java.io.*;
              public class Test {
                  public static void main (String[] args) {
                      Animal h = new Horse();
                      try {
                          h.eat();    
                      }
                      catch (Exception e) {
                      }
                  }
              }

              class Animal {
                  public void eat() throws Exception{
                      System.out.println ("Animal is eating.");
                      throw new Exception();
                  }
              }
              
              class Horse extends Animal{
                  public void eat() throws IOException{
                      System.out.println ("Horse is eating.");
                      throw new IOException();
                  }
              }
          這個例子中,父類拋出了檢查異常Exception,子類拋出的IOException是Exception的子類,也即是比被重寫的方法拋出了更有限的異常,這是可以的。如果反過來,父類拋出IOException,子類拋出更為寬泛的Exception,那么不會通過編譯的。
          注意:這種限制只是針對檢查異常,至于運行時異常RuntimeException及其子類不再這個限制之中。

          E、重寫規則之五:不能重寫被標識為final的方法。

          F、重寫規則之六:如果一個方法不能被繼承,則不能重寫它。
          比較典型的就是父類的private方法。下例會產生一個有趣的現象。
              public class Test {
                  public static void main (String[] args) {
                      //Animal h = new Horse();
                      Horse h = new Horse();
                      h.eat();
                  }
              }

              class Animal {
                  private void eat(){
                      System.out.println ("Animal is eating.");
                  }
              }
              
              class Horse extends Animal{
                  public void eat(){
                      System.out.println ("Horse is eating.");
                  }
              }
          這段代碼是能通過編譯的。表面上看來違反了第六條規則,但實際上那是一點巧合。Animal類的eat()方法不能被繼承,因此Horse類中的eat()方法是一個全新的方法,不是重寫也不是重載,只是一個只屬于Horse類的全新的方法!這點讓很多人迷惑了,但是也不是那么難以理解。
          main()方法如果是這樣:
              Animal h = new Horse();
              //Horse h = new Horse();
              h.eat();
          編譯器會報錯,為什么呢?Horse類的eat()方法是public的啊!應該可以調用啊!請牢記,多態只看父類引用的方法,而不看子類對象的方法!


          二、方法的重載。
          重載是有好的,它不要求你在調用一個方法之前轉換數據類型,它會自動地尋找匹配的方法。方法的重載是在編譯時刻就決定調用哪個方法了,和重寫不同。最最常用的地方就是構造器的重載。

          1、基本數據類型參數的重載。
              public class Test {
                  static void method(byte b){
                      System.out.println ("method:byte");
                  }
                  static void method(short s){
                      System.out.println ("method:short");
                  }
                  static void method(int i){
                      System.out.println ("method:int");
                  }
                  static void method(float f){
                      System.out.println ("method:float");
                  }
                  static void method(double d){
                      System.out.println ("method:double");
                  }
                  public static void main (String[] args) {
                      method((byte)1);
                      method('c');
                      method(1);
                      method(1L);
                      method(1.1);
                      method(1.1f);
                  }
              }
          輸出結果:
          method:byte
          method:int
          method:int
          method:float
          method:double
          method:float

          可以看出:首先要尋找的是數據類型正好匹配方法。如果找不到,那么就提升為表達能力更強的數據類型,如上例沒有正好容納long的整數類型,那么就轉換為float類型的。如果通過提升也不能找到合適的兼容類型,那么編譯器就會報錯。反正是不會自動轉換為較小的數據類型的,必須自己強制轉換,自己來承擔轉變后果。

          char類型比較特殊,如果找不到正好匹配的類型,它會轉化為int而不是short,雖然char是16位的。


          2、重載方法的規則。

          A、被重載的方法必須改變參數列表。
          參數必須不同,這是最重要的!不同有兩個方面,參數的個數,參數的類型,參數的順序。

          B、被重載的方法與返回類型無關。
          也就是說,不能通過返回類型來區分重載方法。

          C、被重載的方法可以改變訪問修飾符。
          沒有重寫方法那樣嚴格的限制。

          D、被重載的方法可以聲明新的或者更廣的檢查異常。
          沒有重寫方法那樣嚴格的限制。

          E、方法能夠在一個類中或者在一個子類中被重載。


          3、帶對象引用參數的方法重載。
              class Animal {}
              class Horse extends Animal{}
              
              public class Test {
                  static void method(Animal a){
                      System.out.println ("Animal is called.");
                  }
                  static void method(Horse h){
                      System.out.println ("Horse is called.");
                  }
                  public static void main (String[] args) {
                      Animal a = new Animal();
                      Horse h = new Horse();
                      Animal ah = new Horse();
                      
                      method(a);
                      method(h);
                      method(ah);
                  }
              }
          輸出結果是:
          Animal is called.
          Horse is called.
          Animal is called.
          前兩個輸出沒有任何問題。第三個方法為什么不是輸出“Horse is called.”呢?還是那句老話,要看引用類型而不是對象類型,方法重載是在編譯時刻就決定的了,引用類型決定了調用哪個版本的重載方法。


          4、重載和重寫方法區別的小結。
          如果能徹底弄明白下面的例子,說明你對重載和重寫非常了解了,可以結束這節的復習了。
              class Animal {
                  public void eat(){
                      System.out.println ("Animal is eating.");    
                  }
              }
              class Horse extends Animal{
                  public void eat(){
                      System.out.println ("Horse is eating.");    
                  }
                  public void eat(String food){
                      System.out.println ("Horse is eating " + food);
                  }
              }
              
              public class Test {
                  public static void main (String[] args) {
                      Animal a = new Animal();
                      Horse h = new Horse();
                      Animal ah = new Horse();
                      
                      a.eat();
                      h.eat();
                      h.eat("apple");
                      ah.eat();
                      //a.eat("apple");
                      //ah.eat("apple");
                  }
              }

          四個輸出分別是什么?被注釋的兩條語句為什么不能通過編譯?
          第一條:a.eat(); 普通的方法調用,沒有多態,沒什么技術含量。調用了Animal類的eat()方法,輸出:Animal is eating.
          第二條:h.eat(); 普通的方法調用,也沒什么技術含量。調用了Horse類的eat()方法,輸出:Horse is eating.
          第三條:h.eat("apple"); 重載。Horse類的兩個eat()方法重載。調用了Horse類的eat(String food)方法,輸出:Horse is eating apple
          第四條:ah.eat(); 多態。前面有例子了,不難理解。輸出:Horse is eating.
          第五條:a.eat("apple"); 低級的錯誤,Animal類中沒有eat(String food)方法。因此不能通過編譯。
          第六條:ah.eat("apple"); 關鍵點就在這里。解決的方法還是那句老話,不能看對象類型,要看引用類型。Animal類中沒有eat(String food)方法。因此不能通過編譯。

          小結一下:多態不決定調用哪個重載版本;多態只有在決定哪個重寫版本時才起作用。
          重載對應編譯時,重寫對應運行時。夠簡潔的了吧!


          三、構造方法。
          構造方法是一種特殊的方法,沒有構造方法就不能創建一個新對象。實際上,不僅要調用對象實際類型的構造方法,還要調用其父類的構造方法,向上追溯,直到Object類。構造方法不必顯式地調用,當使用new關鍵字時,相應的構造方法會自動被調用。

          1、構造方法的規則。
          A、構造方法能使用任何訪問修飾符。包括private,事實上java類庫有很多都是這樣的,設計者不希望使用者創建該類的對象。

          B、構造方法的名稱必須與類名相同。這樣使得構造方法與眾不同,如果我們遵守sun的編碼規范,似乎只有構造方法的首字母是大寫的。

          C、構造方法不能有返回類型。
          反過來說,有返回類型的不是構造方法
              public class Test {
                  int Test(){
                      return 1;
                  }
              }
          這個方法是什么東西?一個冒充李逵的李鬼而已,int Test()和其他任何普通方法沒什么兩樣,就是普通的方法!只不過看起來很惡心,類似惡心的東西在考試卷子里比較多。

          D、如果不在類中創建自己的構造方法,編譯器會自動生成默認的不帶參數的構造函數。
          這點很容易驗證!寫一個這樣簡單的類,編譯。
          class Test {
          }
          對生成的Test.class文件反編譯:javap Test,可以看到:
          D:"JavaCode"bin>javap Test
          Compiled from "Test.java"
          class Test extends java.lang.Object{
              Test();
          }
          看到編譯器自動添加的默認構造函數了吧!

          E、如果只創建了帶參數的構造方法,那么編譯器不會自動添加無參的構造方法的!

          F、在每個構造方法中,如果使用了重載構造函數this()方法,或者父類的構造方法super()方法,那么this()方法或者super()方法必須放在第一行。而且這兩個方法只能選擇一個,因此它們之間沒有順序問題。

          G、除了編譯器生成的構造方法,而且沒有顯式地調用super()方法,那么編譯器會插入一個super()無參調用。

          H、抽象類有構造方法。


          四、靜態方法的重載與重寫(覆蓋)。

          1、靜態方法是不能被覆蓋的。可以分兩種情況討論:

          A、子類的非靜態方法“覆蓋”父類的靜態方法。
          這種情況下,是不能通過編譯的。

          class Father{
              
          static void print(){
                  System.out.println (
          "in father  method");
              }
          }
          class Child extends Father{
              
          void print(){
                  System.out.println (
          "in child method");
              }
          }

          static方法表示該方法不關聯具體的類的對象,可以通過類名直接調用,也就是編譯的前期就綁定了,不存在后期動態綁定,也就是不能實現多態。子類的非靜態方法是與具體的對象綁定的,兩者有著不同的含義。

          B、子類的靜態方法“覆蓋”父類靜態方法。
          這個覆蓋依然是帶引號的。事實上把上面那個例子Child類的print方法前面加上static修飾符,確實能通過編譯!但是不要以為這就是多態!多態的特點是動態綁定,看下面的例子:

          class Father{
              
          static void print(){
                  System.out.println (
          "in father  method");
              }
          }
          class Child extends Father{
              
          static void print(){
                  System.out.println (
          "in child method");
              }
          }

          class Test{
              
          public static void main (String[] args) {
                  Father f 
          =new Child();
                  f.print();
              }
          }

          輸出結果是:in father  method
          從這個結果可以看出,并沒有實現多態。
          但是這種形式很迷惑人,貌似多態,實際編程中千萬不要這樣搞,會把大家搞懵的!
          它不符合覆蓋表現出來的特性,不應該算是覆蓋!
          總而言之,靜態方法不能被覆蓋。

          2、靜態方法可以和非靜態方法一樣被重載。
          這樣的例子太多了,我不想寫例程了。看看java類庫中很多這樣的例子。
          如java.util.Arrays類的一堆重載的binarySearch方法。
          在這里提一下是因為查資料時看到這樣的話“sun的SL275課程說,靜態方法只能控制靜態變量(他們本身沒有),靜態方法不能被重載和覆蓋……”
          大家不要相信啊!可以重載的。而且靜態與非靜態方法可以重載。

          從重載的機制很容易就理解了,重載是在編譯時刻就決定的了,非靜態方法都可以,靜態方法怎么可能不會呢?


          Feedback

          # re: Java語法總結 - 方法  回復  更多評論   

          2007-10-31 12:42 by zhrb
          有一個技術點忘記了,如果父類的方法聲明為static類型的,那么子類可以覆蓋嗎?

          # re: Java語法總結 - 方法[未登錄]  回復  更多評論   

          2007-10-31 14:02 by Hank
          有一個技術點忘記了,如果父類的方法聲明為static類型的,那么子類可以覆蓋嗎?
          =============
          yes, you can do that. as long as it is not final

          # re: Java語法總結 - 方法  回復  更多評論   

          2007-11-02 09:18 by zhrb
          @Hank
          我查了一下,好像不能覆蓋的
          因為是static,無法執行動態綁定(即在編譯的時候就已經確定了)

          # re: Java語法總結 - 方法  回復  更多評論   

          2007-11-02 11:21 by Raylong
          這點我已經補充上了。在最后面。

          # re: Java語法總結 - 方法  回復  更多評論   

          2007-11-02 20:55 by zhrb
          @Raylong
          據說不叫覆蓋,叫隱藏,呵呵

          # re: Java語法總結 - 方法  回復  更多評論   

          2007-11-04 21:42 by Raylong
          @zhrb
          我這里說的覆蓋和重寫是一個意思,隱藏就是“子類的靜態方法覆蓋父類靜態方法”那種情況。我們看的中文書翻譯是各種各樣的,我寫總結的時候也沒能從頭到尾統一起來。

          # re: Java語法總結 - 方法  回復  更多評論   

          2010-06-25 17:49 by huanglang
          寫的不錯,支持樓主

          # re: Java語法總結 - 方法  回復  更多評論   

          2012-03-08 19:06 by 草原上的駱駝
          Static 方法是無法運用多態的
          主站蜘蛛池模板: 衡阳县| 古丈县| 阿拉善右旗| 左云县| 株洲市| 罗田县| 汾阳市| 元江| 西充县| 通辽市| 吐鲁番市| 太白县| 遂宁市| 广南县| 大名县| 延吉市| 化州市| 长治县| 元氏县| 常山县| 九江市| 临颍县| 金门县| 云和县| 康乐县| 建水县| 宁德市| 海伦市| 灵丘县| 宣恩县| 秭归县| 霍州市| 开阳县| 灌南县| 凤庆县| 达拉特旗| 长沙县| 宝鸡市| 视频| 敦化市| 隆回县|