SlideShare una empresa de Scribd logo
1 de 15
Descargar para leer sin conexión
Java SE 7   稿 草冊手術技

    何謂繼承
      繼承?
6.1 何謂繼承?
  物件導向中,子類別繼承(Inherit)父類別,避免重複的行為定義,不過並非為
了避免重複定義行為就使用繼承,濫用繼承而導致程式維護上的問題時有所聞,如何
正確判斷使用繼承的時機,以及繼承之後如何活用多型,才是學習繼承時的重點。


      繼承共同行為
6.1.1 繼承共同行為
  繼承基本上就是避免多個類別間重複定義共同行為。以實際的例子來說明比較清
楚,假設你在正開發一款 RPG(Role-playing game)遊戲,一開始設定的角色有劍
士與魔法師。首先你定義了劍士類別:
public class SwordsMan {
    private String name;          //   稱名色角
    private int level;        //       級等色角
    private int blood;        //       量血色角
    public void fight() {
            System.out.println("   擊攻劍揮   ");
    }


    public int getBlood() {
            return blood;
    }
    public void setBlood(int blood) {
            this.blood = blood;
    }


    public int getLevel() {
            return level;
    }
    public void setLevel(int level) {
            this.level = level;
    }


    public String getName() {
            return name;
    }
    public void setName(String name) {
Java SE 7   稿 草冊手術技
            this.name = name;
    }
}
     接著你為魔法師定義類別:
public class Magician {
    private String name;          //   稱名色角
                                       稱名色角
                                       稱名色角
                                       稱名色角
    private int level;          //     級等色角
                                       級等色角
                                       級等色角
                                       級等色角
    private int blood;          //     量血色角
                                       量血色角
                                       量血色角
                                       量血色角
    public void fight() {
            System.out.println("   擊攻法魔   ");
    }


    public void cure() {
            System.out.println("   療治法魔   ");
    }


    public int getBlood() {
            return blood;
    }
    public void setBlood(int blood) {
            this.blood = blood;
    }


    public int getLevel() {
            return level;
    }
    public void setLevel(int level) {
            this.level = level;
    }


    public String getName() {
            return name;
    }
    public void setName(String name) {
            this.name = name;
    }
}
Java SE 7   稿 草冊手術技
         你注意到什麼呢?因為只要是遊戲中的角色,都會具有角色名稱、等級與血量,
      類別中也都為名稱、等級與血量定義了取值方法與設值方法,Magician 中粗體字部
                                   重複在程式設計上,
                                   重複在程式設計上 就是不好的訊號
                                              不好的訊號。
      份與 SwordsMan 中相對應的程式碼重複了。重複在程式設計上,就是不好的訊號
      舉個例子來說,如果你要將 name、level、blood 改名為其它名稱,那就要修改
      SwordsMan 與 Magician 兩個類別,如果有更多類別具有重複的程式碼,那就要修
      改更多類別,造成維護上的不便。
         如果要改進,可以把相同的程式碼提昇(Pull up)為父類別:
Lab
      Game1        Role.java
      package cc.openhome;


      public class Role {
          private String name;
          private int level;
          private int blood;


          public int getBlood() {
                  return blood;
          }


          public void setBlood(int blood) {
                  this.blood = blood;
          }


          public int getLevel() {
                  return level;
          }


          public void setLevel(int level) {
                  this.level = level;
          }


          public String getName() {
                  return name;
          }


          public void setName(String name) {
                  this.name = name;
          }
Java SE 7   稿 草冊手術技
      }
        這個類別在定義上沒什麼特別的新語法,只不過是將 SwordsMan 與 Magician
Lab
      中重複的程式碼複製過來。接著 SwordsMan 可以如下繼承 Role:
      Game1        SwordsMan.java
      package cc.openhome;


      public class SwordsMan extends Role {
          public void fight() {
                  System.out.println("   擊攻劍揮   ");
          }
      }
         在這邊看到了新的關鍵字 extends,這表示 SwordsMan 會擴充 Role 的行為,
      也就是繼承 Role 的行為,再擴充 Role 原本沒有的 fight()行為。程式面上來說,
      Role 中有定義的程式碼,SwordsMan 中都繼承而擁有了,並再定義了 fight()方
Lab
      法的程式碼。類似地,Magician 也可以如下定義繼承 Role 類別:
      Game1        Magician.java
      package cc.openhome;


      public class Magician extends Role {
          public void fight() {
                  System.out.println("   擊攻法魔   ");
          }


          public void cure() {
                  System.out.println("   療治法魔   ");
          }
      }
        SwordsMan 繼承 Role 的行為 再擴充了 Role 原本沒有的 fight()與 cure()
                             ,
      行為。
Java SE 7   稿 草冊手術技
        提示




                     在這個類別圖中,第一格中 Role 表示類別名稱,第二格中 name、
                     level 、 blood 表 示 資 料 成 員 , : 號 之 後 為 各 成 員 型 態 , - 號 表 示
                     private,第三格表示方法名稱,+號表示 public,:號之後表示傳回
                     型態,繼承則以空心箭頭表示。

Lab    如何看出確實有繼承了呢?以下簡單的程式可以看出:
      Game1        RPG.java
      package cc.openhome;


      public class RPG {
          public static void main(String[] args) {
                  SwordsMan swordsMan = new SwordsMan();
                  swordsMan.setName("Justin");
                  swordsMan.setLevel(1);
                  swordsMan.setBlood(200);
                  System.out.printf("   :士劍   (%s, %d, %d)%n", swordsMan.getName(),
                        swordsMan.getLevel(), swordsMan.getBlood());


                  Magician magician = new Magician();
                  magician.setName("Monica");
Java SE 7   稿 草冊手術技
            magician.setLevel(1);
            magician.setBlood(100);
            System.out.printf("   :師法魔   (%s, %d, %d)%n", magician.getName(),
                  magician.getLevel(), magician.getBlood());
    }
}
    雖然 SwordsMan 與 Magician 並沒有定義 getName()、getLevel()與
getBlood()等方法,但從 Role 繼承了這些方法,所以就如範例中可以直接使用,
執行的結果如下:
 :士劍    (Justin, 1, 200)
:師法魔        (Monica, 1, 100)
  繼承的好處之一,就是若你要將 name、level、blood 改名為其它名稱,那就
只要修改 Role.java 就可以了,只要是繼承 Role 的子類別都無需修改。
    注意         有 的 書 籍 或 文 件 會 說 , private 成 員 無 法 繼 承 , 那 是 錯 的 ! 如 果
               private 成員無法繼承,那為什麼上面的範例 name、level、blood
               記錄的值會顯示出來呢?private 成員會被繼承,只不過子類別無法直
               接存取,必須透過父類別提供的方法來存取(如果父類別願意提供存取
               方法的話)
                   。


6.1.2 多型與 is-a
            子類別只能繼承一個父類別,繼承除了可避免類別間重複的行為定
   在 Java 中,子類別只能繼承一個父類別
            子類別只能繼承一個父類別
義外,還有個重要的關係,那就是子類別與父類別間會有 is-a 的關係,中文稱為「是                「
一種」
一種」的關係,這是什麼意思?以先前範例來說,SwordsMan 繼承了 Role,所以
SwordsMan 是一種 Role(SwordsMan is a Role)
                  (                   ),Magician 繼承了 Role,所
以 Magician 是一種 Role(Magician is a Role)
                   (                   )。
   為何要知道繼承時,父類別與子類別間會有「是一種」的關係?因為要開始理解
多型(Polymorphism)
               ,必須先知道你操作的物件是「哪一種」東西!
  來看實際的例子,以下的程式碼片段,相信你現在沒有問題地看懂,而且知道可
以通過編譯:
SwordsMan swordsMan = new SwordsMan();
Magician magician = new Magician();
     那你知道以下的程式片段也可以通過編譯嗎?
Role role1 = new SwordsMan();
Role role2 = new Magician();
     那你知道以下的程式片段為何無法通過編譯呢?
SwordsMan swordsMan = new Role();
Magician magician = new Role();
Java SE 7   稿 草冊手術技
  編譯器就是語法檢查器,要知道以上程式片段為何可以通過編譯,為何無法通過
編譯,就是將自己當作編譯器,檢查語法的邏輯是否正確,方式是從 號右邊往左讀:
                          方式是從=號右邊往左讀
                          方式是從
右邊是不是一種左邊呢(右邊型態是不是左邊型態的子類別)?




               圖 5.1 運用 is a 關係判斷語法正確性
   從右往左讀,SwordsMan 是不是一 種 Role 呢?是的 ! 所以編譯通過。
Magician 是不是一種 Role 呢?是的!所以編譯通過。同樣的判斷方式,可以知道
為何以下編譯失敗:
SwordsMan swordsMan = new Role();   // Role   種一是不是   SwordsMan  ?
Magician magician = new Role();     // Role   種一是不是   Magician    ?
     編譯器認為第一行,Role 不一定是一種 SwrodsMan,所以編譯失敗,對於第
二行,編譯器認為 Role 不一定是一種 Magician,所以編譯失敗。繼續把自己當成
編譯器,再來看看以下的程式片段是否可以通過編譯:
Role role1 = new SwordsMan();
SwordsMan swordsMan = role1;
     這個程式片段最後會編譯失敗,先從第一行看,SwordsMan 是一種 Role,所
以這行可以通過編譯。編譯器檢查這類語法,一次只看一行,就第二行而言,編譯器
看到 role1 為 Role 宣告的名稱,於是檢查 Role 是不是一種 SwordsMan,答案是
不一定,所以編譯失敗在第二行!
  編譯器會檢查父子類別間的「是一種」關係,如果你不想要編譯器囉嗦,可以叫
它住嘴:
Role role1 = new SwordsMan();
SwordsMan swordsMan = (SwordsMan) role1;
   對於第二行,原本編譯器想囉嗦地告訴你,Role 不一定是一種 SwordsMan,
但你加上了(SwordsMan)讓它住嘴了,因為這表示,你就是要讓 Role 扮演 CAST)
                                         (    )
SwrodsMan,既然你都明確要求編譯器別囉嗦了,編譯器就讓這段程式碼通過編譯
  不過後果得自行負責!
了,不過後果得自行負責
  不過後果得自行負責
Java SE 7   稿 草冊手術技
   以上面這個程式片段來說,role1 確實參考至 SwordsMan 實例,所以在第二行
              扮演
讓 SwordsMan 實例扮演 SwordsMan 並沒有什麼問題,所以執行時期並不會出錯。




             圖 5.2 判斷是否可扮演(CAST)成功
                   判斷是否可扮演(    )
     但是以下的程式片段,編譯可以成功,但執行時期會出錯:
Role role2 = new Magician();
SwordsMan swordsMan = (SwordsMan) role2;
   對於第一行,Magician 是一種 Role,可以通過編譯,對於第二行,role2 為
Role 型態,編譯器原本認定 Role 不一定是一種 SwordsMan 而想要囉嗦,但是你
明確告訴編譯器,就是要讓 Role 扮演為 SwordsMan,所以編譯器就讓你通過編譯
了,不過後果自負,實際上,role2 參考的是 Magician,你要讓魔法師假扮為劍
士?這在執行上會是個錯誤,JVM 會拋出 java.lang.ClassCastException。




             扮演(    )失敗,執行時拋出
       圖 5.3 扮演(CAST)失敗,執行時拋出 ClassCastException
     使用有一種(is-a)原則,你就可以判斷,何時編譯成功,何時編譯失敗,以及
Java SE 7   稿 草冊手術技
將扮演(CAST)看作是叫編譯器住嘴語法,並留意參考的物件實際型態,你就可以
判斷何時扮演成功,何時會拋出 ClassCastException。例如以下編譯成功,執行
也沒問題:
SwordsMan swordsMan = new SwordsMan();
Role role = swordsMan;           // SwordsMan   種一是    Role
     以下程式片段會編譯失敗:
SwordsMan swordsMan = new SwordsMan();
Role role = swordsMan;                         種一是
                                     // SwordsMan             Role    譯編 過通行 這,
SwordsMan swordsMan = role;          // Role   種一是定一不                敗失譯 編,
                                                              SwordsMan
     以下程式片段編譯成功,執行時也沒問題:
SwordsMan swordsMan = new SwordsMan();
Role role = swordsMan;            // SwordsMan    種一是   Role   譯 編過 通行這 ,
//   讓要器 譯編訴告你      Role   演扮   SwordsMan     譯編 過通行這 下以,
SwordsMan swordsMan = (SwordsMan) role;     考參      // role          SwordsMan   功成行 執,例實
     以下程式片段編譯成功,但執行時拋出 ClassCastException:
SwordsMan swordsMan = new SwordsMan();
Role role = swordsMan;           // SwordsMan 種一是       Role   譯編 過通行 這,
//   讓要器 譯編訴告你      Role   演扮   Magician   譯 編過通行 這 下以,
                                           考參
Magician magician = (Magician) role; // role                   SwordsMan   敗失 行執,例 實
  經過以上這一連串的語法測試,好像只是在玩弄語法?不!你懂不懂以上這些東
西,牽涉到寫出來的東西有沒有彈性、好不好維護的問題!
  有這麼嚴重嗎?來出個題目給你吧!請設計 static 方法,顯示所有角色的血
量!OK!上一章剛學過如何定義方法,有的人會撰寫以下的方法定義:
public static void showBlood(SwordsMan swordsMan) {
     System.out.printf("%s      量血   %d%n",
             swordsMan.getName(), swordsMan.getBlood());
}
public static void showBlood(Magician magician) {
     System.out.printf("%s      量血   %d%n",
             magician.getName(), magician.getBlood());
}
     分別為 SwordsMan 與 Magician 設計 showBlood()同名方法,這是重載方法
的運用,如此就可以如下呼叫:
showBlood(swordsMan);           // swordsMan  是            態型
                                                   SwordsMan
showBlood(magician);        // magician        是Magician    態型
  現在的問題是,目前你的遊戲中是只有 SwordsMan 與 Magician 兩個角色,如
果有一百個角色呢?重載出一百個方法?這種方式顯然不可能!如果所有角色都是
繼承自 Role,而且你知道這些角色都是一種 Role,你就可以如下設計方法並呼叫:
Java SE 7   稿 草冊手術技
Game2        RPG.java
package cc.openhome;
                                           宣告為 Role 型態
public class RPG {
    public static void showBlood(Role role) {
            System.out.printf("%s   量血   %d%n",
                   role.getName(), role.getBlood());
    }


    public static void main(String[] args) {
            SwordsMan swordsMan = new SwordsMan();
            swordsMan.setName("Justin");
            swordsMan.setLevel(1);
            swordsMan.setBlood(200);


            Magician magician = new Magician();
            magician.setName("Monica");
            magician.setLevel(1);
            magician.setBlood(100);


            showBlood(swordsMan);      SwordsMan 是一種 Role
            showBlood(magician);       Magician 是一種 Role
    }
}
   在這邊僅定義了一個 showBlood()方法,參數宣告為 Role 型態 ,第一次呼
叫 showBlood()時傳入了 SwordsMan 實例,這是合法的語法,因為 SwordsMan
是一種 Role ,第一次呼叫 showBlood()時傳入了 Magician 實例也是可行,因
為 Magician 是一種 Role 。執行的結果如下:
Justin      量血   200
Monica      量血   100
     這樣的寫法好處為何?就算有 100 種角色,只要它們都是繼承 Role,都可以使
用這個方法顯示角色的血量,而不需要像先前重載的方式,為不同角色寫 100 個方
法,多型的寫法顯然具有更高的可維護性。
                       使用單一介面操作多種型態的物件!若用以
                       使用單一介面操作多種型態的物件
  什麼叫多型?以抽象講法解釋,就是使用單一介面操作多種型態的物件
上的範例來理解,在 showBlood()方法中,既可以透過 Role 型態操作 SwordsMan
物件,也可以透過 Role 型態操作 Magician 物件。
    注意         稍後會學到 Java 中 interface 的使用,在多型定義中,使用單一介面
               操作多種型態的物件,這邊的介面並不是專指 Java 中的 interface,
Java SE 7   稿 草冊手術技
                       而是指物件上可操作的方法。


            重新定義行為
      6.1.3 重新定義行為
        現在有個需求,請設計 static 方法,可以播放角色攻擊動畫,你也許會這麼想,
      學剛剛多型的寫法,設計個 drawFight()方法如何?




                         圖 5.4 Role 沒有定義 fight()方法
                                                方法
           對 drawFight()方法而言,只知道傳進來的會是一種 Role 物件,所以編譯器
      也只能檢查你呼叫的方法,Role 是不是有定義,顯然地,Role 目前並沒有定義
      fight()方法,因此編譯錯誤了。
         然而你仔細觀察一下 SwordsMan 與 Magician 的 fight()方法,他們的方法
                                                       方法
      簽署(
      簽署(method signature)都是:
                         )
      public void fight()
        也就是說,操作介面是相同的,只是方法實作內容不同,你可以將 fight()方
Lab
      法提昇至 Role 類別中定義:
      Game3        Role.java
      package cc.openhome;


      public class Role {
          ...   略
          public void fight() {
                  //   義定新重要別類子
                       義定新重要別類子
                       義定新重要別類子
                       義定新重要別類子         為行際實的
                                        為行際實的
                                        為行際實的
                                  fight()
                                        為行際實的
          }
      }
        在 Role 類別中定義了 fight()方法,由於實際上角色如何攻擊,只有子類別
      才知道,所以這邊的 fight()方法內容是空的,沒有任何程式碼實作。SwordsMan
      繼承 Role 之後,再對 fight()的行為進行定義:
      Game3        SwordsMan.java
      package cc.openhome;


      public class SwordsMan extends Role {
Java SE 7   稿 草冊手術技
    public void fight() {
            System.out.println("   擊攻劍揮
                                   擊攻劍揮
                                   擊攻劍揮
                                   擊攻劍揮");
    }
}
  在繼承父類別之後,定義與父類別中相同的方法簽署,但實作內容不同,這稱為
重新定義(
重新定義(Override)
             ),因為對父類別中已定義的方法實作不滿意,所以你在子類別
中重新定義實作。Magician 繼承 Role 之後,也重新定義了 fight()的行為:
Game3        Magician.java
package cc.openhome;


public class Magician extends Role {
    public void fight() {
            System.out.println("   擊攻法魔
                                   擊攻法魔
                                   擊攻法魔
                                   擊攻法魔");
    }
    ...   略
}
  由於 Role 現在定義了 fight()方法(雖然方法區塊中沒有程式碼實作)
                                        ,所以
編譯器不會找不到 Role 的 fight()了,因此你可以如下撰寫:
Game3        RPG.java
package cc.openhome;

                                    宣告為 Role 型態
public class RPG {
    public static void drawFight(Role role) {
            System.out.print(role.getName());
            role.fight();
    }


    public static void main(String[] args) {
            SwordsMan swordsMan = new SwordsMan();
            swordsMan.setName("Justin");
            swordsMan.setLevel(1);
            swordsMan.setBlood(200);


            Magician magician = new Magician();
            magician.setName("Monica");
            magician.setLevel(1);
            magician.setBlood(100);
Java SE 7   稿 草冊手術技
            drawFight(swordsMan);   實際操作的是 SwordsMan 實例
            drawFight(magician);    實際操作的是 Magician 實例
    }
}
   在 fight()方法宣告了 Role 型態的參數 ,那方法中呼叫的,到底是 Role 中
定義的 fight(),還是個別子類別中定義的 fight()呢?如果傳入 fight()的是
SwordsMan,role 參數參考的就是 SwordsMan 實例,操作的就是 SwordsMan 上
的方法定義:




               圖 5.5 role 牌子掛在 SwordsMan 實例
   這就好比 role 牌子掛在 SwordsMan 實例身上,你要求有 role 牌子的物件攻
擊,發動攻擊的物件就是 SwordsMan 實例。同樣地,如果傳入 fight()的是
Magician,role 參數參考的就是 Magician 實例,操作的就是 Magician 上的方
法定義:




             圖 5.6 role 牌子掛在 Magician 實例
     所以範例最後的執行結果是:
Justin   擊攻劍揮
Monica   擊攻法魔
  在重新定義父類別中某個方法時,子類別必須撰寫與父類別方法中相同的簽署,
然而如果疏忽打錯字了:
public class SwordsMan extends Role {
Java SE 7   稿 草冊手術技
          public void Fight() {
                  System.out.println("   擊攻劍揮   ");
          }
      }
         以這邊的例子來說,父類別中定義的是 fight(),但子類別中定義了 Fight(),
      這就不是重新定義 fight()了,而是子類別新定義了一個 Fight()方法,這是合法
      的方法定義,編譯器並不會發出任何錯誤訊息,你只會在運行範例時,發現為什麼
      SwordsMan 完全沒有攻擊。
         在 JDK5 之 後 支 援 標 註 ( Annotation ) 其 中 一 個 內 建 的 標 準 標 註 就 是
                                          ,
      @Override,如果你在子類別中某個方法前標註@Override,表示要求編譯器檢
      查,該方法是不是真的重新定義了父類別中某個方法,如果不是的話,就會引發編譯
      錯誤。例如:




             圖 5.7 編譯器檢查是否真的重新定義父類別某方法
                   編譯器檢查是否真的重新定義父類別某方法
        如果你要重新定義某方法,加上@Override,就不用擔心打錯字的問題了。關
      於標註詳細語法,會在之後章節說明。


            抽象方法、
      6.1.4 抽象方法、抽象類別
        上一個範例中 Role 類別的定義中,fight()方法區塊中實際上沒有撰寫任何程
      式碼,雖然滿足了多型需求,但會引發的問題是,你沒有任何方式強迫或提示子類別
      一定要實作 fight()方法,只能口頭或在文件上告知,不過如果有人沒有傳達到、
      沒有看文件或文件看漏了呢?
        如果某方法區塊中真的沒有任何程式碼實作,可以使用 abstract 標示該方法
Lab
      為抽象方法(Abstract method)
       抽象方法(
       抽象方法                ,該方法不用撰寫{}區塊,直接;結束即可。例如:
                           )
      Game4        Role.java
      package cc.openhome;


      public abstract class Role {
          ...   略
Java SE 7   稿 草冊手術技
      public abstract void fight();
}
  類別中若有方法沒有實作,並且標示為 abstract,表示這個類別定義不完整,
定義不完整的類別就不能用來生成實例
           就不能用來生成實例,這就好比設計圖不完整,不能用來生產成品
定義不完整的類別就不能用來生成實例
        中規定內含抽象方法的類別
                 的類別,
一樣。Java 中規定內含抽象方法的類別,一定要在 class 前標示 abstract,如上
                    抽象類別(
例所示,這表示這是一個定義不完整的抽象類別(Abstract class)
                    抽象類別            )。如果嘗試用抽
象類別建構實例,就會引發編譯錯誤:




                圖 5.8 不能實例化抽象類別
  子類別如果繼承抽象類別,對於抽象方法有兩種作法,一種作法是繼續標示該方
法為 abstract
          (該子類別因此也是個抽象類別 必須在 class 前標示 abstract)
                           ,                   ,
另一個作法就是實作抽象方法。如果兩個作法都沒實施,就會引發編譯錯誤:




                               圖 5.9 沒有實作抽象方法

6.2 繼承語法細節
  上一節介紹了繼承的基礎觀念與語法,然而結合 Java 的特性,繼承還有許多細
節必須明瞭 像是哪些成員可以限定在子類別中使用 哪些方法簽署算重新定義 Java
     ,                        、           、
中所有物件都是一種 java.lang.Object 等細節,這將於本節中詳細說明。


6.2.1protected 成員
  就上一節的 RPG 遊戲來說,如果建立了一個角色,想顯示角色的細節,必須如
下撰寫:
SwordsMan swordsMan = new SwordsMan();
...   略
System.out.printf("     士劍
                        士劍
                        士劍
                        士劍   (%s, %d, %d)%n", swordsMan.getName(),
            swordsMan.getLevel(), swordsMan.getBlood());
Magician magician = new Magician();
...   略
System.out.printf("     師法魔
                        師法魔
                        師法魔
                        師法魔   (%s, %d, %d)%n", magician.getName(),

Más contenido relacionado

La actualidad más candente

Java面试题集
Java面试题集Java面试题集
Java面试题集
yiditushe
 
Java SE 7 技術手冊投影片第 15 章 - 反射器與類別載入器
Java SE 7 技術手冊投影片第 15 章 - 反射器與類別載入器Java SE 7 技術手冊投影片第 15 章 - 反射器與類別載入器
Java SE 7 技術手冊投影片第 15 章 - 反射器與類別載入器
Justin Lin
 
Python面向对象开发基础篇
Python面向对象开发基础篇Python面向对象开发基础篇
Python面向对象开发基础篇
modou li
 
Java SE 7 技術手冊投影片第 16 章 - 自訂泛型、列舉與標註
Java SE 7 技術手冊投影片第 16 章 - 自訂泛型、列舉與標註Java SE 7 技術手冊投影片第 16 章 - 自訂泛型、列舉與標註
Java SE 7 技術手冊投影片第 16 章 - 自訂泛型、列舉與標註
Justin Lin
 
Java SE 7 技術手冊投影片第 11 章 - 執行緒與並行API
Java SE 7 技術手冊投影片第 11 章 - 執行緒與並行APIJava SE 7 技術手冊投影片第 11 章 - 執行緒與並行API
Java SE 7 技術手冊投影片第 11 章 - 執行緒與並行API
Justin Lin
 
大公司的Java面试题集
大公司的Java面试题集大公司的Java面试题集
大公司的Java面试题集
yiditushe
 
Java SE 7 技術手冊投影片第 06 章 - 繼承與多型
Java SE 7 技術手冊投影片第 06 章 - 繼承與多型Java SE 7 技術手冊投影片第 06 章 - 繼承與多型
Java SE 7 技術手冊投影片第 06 章 - 繼承與多型
Justin Lin
 
[圣思园][Java SE]Reflection
[圣思园][Java SE]Reflection[圣思园][Java SE]Reflection
[圣思园][Java SE]Reflection
ArBing Xie
 
Java面试笔试题大汇总
Java面试笔试题大汇总Java面试笔试题大汇总
Java面试笔试题大汇总
yiditushe
 
Java相关基础知识
Java相关基础知识Java相关基础知识
Java相关基础知识
yiditushe
 
Java SE 7 技術手冊投影片第 09 章 - Collection與Map
Java SE 7 技術手冊投影片第 09 章 - Collection與MapJava SE 7 技術手冊投影片第 09 章 - Collection與Map
Java SE 7 技術手冊投影片第 09 章 - Collection與Map
Justin Lin
 
Java SE 7 技術手冊投影片第 14 章 - 整合資料庫
Java SE 7 技術手冊投影片第 14 章 - 整合資料庫Java SE 7 技術手冊投影片第 14 章 - 整合資料庫
Java SE 7 技術手冊投影片第 14 章 - 整合資料庫
Justin Lin
 

La actualidad más candente (20)

Java Script 引擎技术
Java Script 引擎技术Java Script 引擎技术
Java Script 引擎技术
 
Java SE 7 技術手冊投影片第 04 章 - 認識物件
Java SE 7 技術手冊投影片第 04 章 - 認識物件Java SE 7 技術手冊投影片第 04 章 - 認識物件
Java SE 7 技術手冊投影片第 04 章 - 認識物件
 
Java面试题集
Java面试题集Java面试题集
Java面试题集
 
Java SE 7 技術手冊投影片第 12 章 - 通用API
Java SE 7 技術手冊投影片第 12 章  - 通用APIJava SE 7 技術手冊投影片第 12 章  - 通用API
Java SE 7 技術手冊投影片第 12 章 - 通用API
 
Java SE 7 技術手冊投影片第 15 章 - 反射器與類別載入器
Java SE 7 技術手冊投影片第 15 章 - 反射器與類別載入器Java SE 7 技術手冊投影片第 15 章 - 反射器與類別載入器
Java SE 7 技術手冊投影片第 15 章 - 反射器與類別載入器
 
第二章
第二章第二章
第二章
 
Python面向对象开发基础篇
Python面向对象开发基础篇Python面向对象开发基础篇
Python面向对象开发基础篇
 
Java SE 7 技術手冊投影片第 16 章 - 自訂泛型、列舉與標註
Java SE 7 技術手冊投影片第 16 章 - 自訂泛型、列舉與標註Java SE 7 技術手冊投影片第 16 章 - 自訂泛型、列舉與標註
Java SE 7 技術手冊投影片第 16 章 - 自訂泛型、列舉與標註
 
Java SE 7 技術手冊投影片第 11 章 - 執行緒與並行API
Java SE 7 技術手冊投影片第 11 章 - 執行緒與並行APIJava SE 7 技術手冊投影片第 11 章 - 執行緒與並行API
Java SE 7 技術手冊投影片第 11 章 - 執行緒與並行API
 
大公司的Java面试题集
大公司的Java面试题集大公司的Java面试题集
大公司的Java面试题集
 
Java SE 7 技術手冊投影片第 06 章 - 繼承與多型
Java SE 7 技術手冊投影片第 06 章 - 繼承與多型Java SE 7 技術手冊投影片第 06 章 - 繼承與多型
Java SE 7 技術手冊投影片第 06 章 - 繼承與多型
 
[圣思园][Java SE]Reflection
[圣思园][Java SE]Reflection[圣思园][Java SE]Reflection
[圣思园][Java SE]Reflection
 
Java面试笔试题大汇总
Java面试笔试题大汇总Java面试笔试题大汇总
Java面试笔试题大汇总
 
Java物件導向
Java物件導向Java物件導向
Java物件導向
 
Java相关基础知识
Java相关基础知识Java相关基础知识
Java相关基础知识
 
Java SE 7 技術手冊投影片第 09 章 - Collection與Map
Java SE 7 技術手冊投影片第 09 章 - Collection與MapJava SE 7 技術手冊投影片第 09 章 - Collection與Map
Java SE 7 技術手冊投影片第 09 章 - Collection與Map
 
组合、备忘录、建造者模式、原型
组合、备忘录、建造者模式、原型组合、备忘录、建造者模式、原型
组合、备忘录、建造者模式、原型
 
Java SE 7 技術手冊投影片第 10 章 - 輸入輸出
Java SE 7 技術手冊投影片第 10 章 - 輸入輸出Java SE 7 技術手冊投影片第 10 章 - 輸入輸出
Java SE 7 技術手冊投影片第 10 章 - 輸入輸出
 
Java SE 7 技術手冊投影片第 14 章 - 整合資料庫
Java SE 7 技術手冊投影片第 14 章 - 整合資料庫Java SE 7 技術手冊投影片第 14 章 - 整合資料庫
Java SE 7 技術手冊投影片第 14 章 - 整合資料庫
 
Java SE 7 技術手冊投影片第 08 章 - 例外處理
Java SE 7 技術手冊投影片第 08 章 - 例外處理Java SE 7 技術手冊投影片第 08 章 - 例外處理
Java SE 7 技術手冊投影片第 08 章 - 例外處理
 

Destacado

Java SE 7 技術手冊投影片第 07 章 - 介面與多型
Java SE 7 技術手冊投影片第 07 章 - 介面與多型Java SE 7 技術手冊投影片第 07 章 - 介面與多型
Java SE 7 技術手冊投影片第 07 章 - 介面與多型
Justin Lin
 
Java SE 7 技術手冊投影片第 13 章 - 視窗程式設計
Java SE 7 技術手冊投影片第 13 章 - 視窗程式設計Java SE 7 技術手冊投影片第 13 章 - 視窗程式設計
Java SE 7 技術手冊投影片第 13 章 - 視窗程式設計
Justin Lin
 
Java SE 7 技術手冊投影片第 01 章 - Java平台概論
Java SE 7 技術手冊投影片第 01 章 - Java平台概論Java SE 7 技術手冊投影片第 01 章 - Java平台概論
Java SE 7 技術手冊投影片第 01 章 - Java平台概論
Justin Lin
 

Destacado (11)

Java SE 7 技術手冊第七章草稿 - 何謂介面?
Java SE 7 技術手冊第七章草稿 - 何謂介面?Java SE 7 技術手冊第七章草稿 - 何謂介面?
Java SE 7 技術手冊第七章草稿 - 何謂介面?
 
Java SE 7 技術手冊第五章草稿 - 何謂封裝?
Java SE 7 技術手冊第五章草稿 - 何謂封裝?Java SE 7 技術手冊第五章草稿 - 何謂封裝?
Java SE 7 技術手冊第五章草稿 - 何謂封裝?
 
Java SE 7 技術手冊投影片第 07 章 - 介面與多型
Java SE 7 技術手冊投影片第 07 章 - 介面與多型Java SE 7 技術手冊投影片第 07 章 - 介面與多型
Java SE 7 技術手冊投影片第 07 章 - 介面與多型
 
Java SE 7 技術手冊投影片第 05 章 - 物件封裝
Java SE 7 技術手冊投影片第 05 章  - 物件封裝Java SE 7 技術手冊投影片第 05 章  - 物件封裝
Java SE 7 技術手冊投影片第 05 章 - 物件封裝
 
Java SE 7 技術手冊投影片第 13 章 - 視窗程式設計
Java SE 7 技術手冊投影片第 13 章 - 視窗程式設計Java SE 7 技術手冊投影片第 13 章 - 視窗程式設計
Java SE 7 技術手冊投影片第 13 章 - 視窗程式設計
 
Java SE 7 技術手冊投影片第 02 章 - 從JDK到IDE
Java SE 7 技術手冊投影片第 02 章 - 從JDK到IDEJava SE 7 技術手冊投影片第 02 章 - 從JDK到IDE
Java SE 7 技術手冊投影片第 02 章 - 從JDK到IDE
 
Java SE 7 技術手冊 - 課後練習解答
Java SE 7 技術手冊 - 課後練習解答Java SE 7 技術手冊 - 課後練習解答
Java SE 7 技術手冊 - 課後練習解答
 
Java SE 8 技術手冊第 1 章 - Java平台概論
Java SE 8 技術手冊第 1 章 - Java平台概論Java SE 8 技術手冊第 1 章 - Java平台概論
Java SE 8 技術手冊第 1 章 - Java平台概論
 
Java SE 7 技術手冊投影片第 01 章 - Java平台概論
Java SE 7 技術手冊投影片第 01 章 - Java平台概論Java SE 7 技術手冊投影片第 01 章 - Java平台概論
Java SE 7 技術手冊投影片第 01 章 - Java平台概論
 
Java SE 7 技術手冊投影片第 03 章 - 基礎語法
Java SE 7 技術手冊投影片第 03 章 - 基礎語法Java SE 7 技術手冊投影片第 03 章 - 基礎語法
Java SE 7 技術手冊投影片第 03 章 - 基礎語法
 
Java SE 8 技術手冊第 2 章 - 從JDK到IDE
Java SE 8 技術手冊第 2 章 - 從JDK到IDEJava SE 8 技術手冊第 2 章 - 從JDK到IDE
Java SE 8 技術手冊第 2 章 - 從JDK到IDE
 

Más de Justin Lin

Más de Justin Lin (20)

Ch14 簡介 Spring Boot
Ch14 簡介 Spring BootCh14 簡介 Spring Boot
Ch14 簡介 Spring Boot
 
Ch13 整合 Spring MVC/Security
Ch13 整合 Spring MVC/SecurityCh13 整合 Spring MVC/Security
Ch13 整合 Spring MVC/Security
 
Ch12 Spring 起步走
Ch12 Spring 起步走Ch12 Spring 起步走
Ch12 Spring 起步走
 
Ch11 簡介 JavaMail
Ch11 簡介 JavaMailCh11 簡介 JavaMail
Ch11 簡介 JavaMail
 
Ch10 Web 容器安全管理
Ch10 Web 容器安全管理Ch10 Web 容器安全管理
Ch10 Web 容器安全管理
 
Ch09 整合資料庫
Ch09 整合資料庫Ch09 整合資料庫
Ch09 整合資料庫
 
Ch08 自訂標籤
Ch08 自訂標籤Ch08 自訂標籤
Ch08 自訂標籤
 
Ch07 使用 JSTL
Ch07 使用 JSTLCh07 使用 JSTL
Ch07 使用 JSTL
 
Ch06 使用 JSP
Ch06 使用 JSPCh06 使用 JSP
Ch06 使用 JSP
 
Ch05 Servlet 進階 API、過濾器與傾聽器
Ch05 Servlet 進階 API、過濾器與傾聽器Ch05 Servlet 進階 API、過濾器與傾聽器
Ch05 Servlet 進階 API、過濾器與傾聽器
 
Ch04 會話管理
Ch04 會話管理Ch04 會話管理
Ch04 會話管理
 
Ch03 請求與回應
Ch03 請求與回應Ch03 請求與回應
Ch03 請求與回應
 
Ch02 撰寫與設定 Servlet
Ch02 撰寫與設定 ServletCh02 撰寫與設定 Servlet
Ch02 撰寫與設定 Servlet
 
CH1. 簡介 Web 應用程式
CH1. 簡介 Web 應用程式CH1. 簡介 Web 應用程式
CH1. 簡介 Web 應用程式
 
14. 進階主題
14. 進階主題14. 進階主題
14. 進階主題
 
13.並行、平行與非同步
13.並行、平行與非同步13.並行、平行與非同步
13.並行、平行與非同步
 
12. 除錯、測試與效能
12. 除錯、測試與效能12. 除錯、測試與效能
12. 除錯、測試與效能
 
11. 常用內建模組
11. 常用內建模組11. 常用內建模組
11. 常用內建模組
 
10. 資料永續與交換
10. 資料永續與交換10. 資料永續與交換
10. 資料永續與交換
 
9. 資料結構
9. 資料結構9. 資料結構
9. 資料結構
 

Java SE 7 技術手冊第六章草稿 - 何謂繼承?

  • 1. Java SE 7 稿 草冊手術技 何謂繼承 繼承? 6.1 何謂繼承? 物件導向中,子類別繼承(Inherit)父類別,避免重複的行為定義,不過並非為 了避免重複定義行為就使用繼承,濫用繼承而導致程式維護上的問題時有所聞,如何 正確判斷使用繼承的時機,以及繼承之後如何活用多型,才是學習繼承時的重點。 繼承共同行為 6.1.1 繼承共同行為 繼承基本上就是避免多個類別間重複定義共同行為。以實際的例子來說明比較清 楚,假設你在正開發一款 RPG(Role-playing game)遊戲,一開始設定的角色有劍 士與魔法師。首先你定義了劍士類別: public class SwordsMan { private String name; // 稱名色角 private int level; // 級等色角 private int blood; // 量血色角 public void fight() { System.out.println(" 擊攻劍揮 "); } public int getBlood() { return blood; } public void setBlood(int blood) { this.blood = blood; } public int getLevel() { return level; } public void setLevel(int level) { this.level = level; } public String getName() { return name; } public void setName(String name) {
  • 2. Java SE 7 稿 草冊手術技 this.name = name; } } 接著你為魔法師定義類別: public class Magician { private String name; // 稱名色角 稱名色角 稱名色角 稱名色角 private int level; // 級等色角 級等色角 級等色角 級等色角 private int blood; // 量血色角 量血色角 量血色角 量血色角 public void fight() { System.out.println(" 擊攻法魔 "); } public void cure() { System.out.println(" 療治法魔 "); } public int getBlood() { return blood; } public void setBlood(int blood) { this.blood = blood; } public int getLevel() { return level; } public void setLevel(int level) { this.level = level; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
  • 3. Java SE 7 稿 草冊手術技 你注意到什麼呢?因為只要是遊戲中的角色,都會具有角色名稱、等級與血量, 類別中也都為名稱、等級與血量定義了取值方法與設值方法,Magician 中粗體字部 重複在程式設計上, 重複在程式設計上 就是不好的訊號 不好的訊號。 份與 SwordsMan 中相對應的程式碼重複了。重複在程式設計上,就是不好的訊號 舉個例子來說,如果你要將 name、level、blood 改名為其它名稱,那就要修改 SwordsMan 與 Magician 兩個類別,如果有更多類別具有重複的程式碼,那就要修 改更多類別,造成維護上的不便。 如果要改進,可以把相同的程式碼提昇(Pull up)為父類別: Lab Game1 Role.java package cc.openhome; public class Role { private String name; private int level; private int blood; public int getBlood() { return blood; } public void setBlood(int blood) { this.blood = blood; } public int getLevel() { return level; } public void setLevel(int level) { this.level = level; } public String getName() { return name; } public void setName(String name) { this.name = name; }
  • 4. Java SE 7 稿 草冊手術技 } 這個類別在定義上沒什麼特別的新語法,只不過是將 SwordsMan 與 Magician Lab 中重複的程式碼複製過來。接著 SwordsMan 可以如下繼承 Role: Game1 SwordsMan.java package cc.openhome; public class SwordsMan extends Role { public void fight() { System.out.println(" 擊攻劍揮 "); } } 在這邊看到了新的關鍵字 extends,這表示 SwordsMan 會擴充 Role 的行為, 也就是繼承 Role 的行為,再擴充 Role 原本沒有的 fight()行為。程式面上來說, Role 中有定義的程式碼,SwordsMan 中都繼承而擁有了,並再定義了 fight()方 Lab 法的程式碼。類似地,Magician 也可以如下定義繼承 Role 類別: Game1 Magician.java package cc.openhome; public class Magician extends Role { public void fight() { System.out.println(" 擊攻法魔 "); } public void cure() { System.out.println(" 療治法魔 "); } } SwordsMan 繼承 Role 的行為 再擴充了 Role 原本沒有的 fight()與 cure() , 行為。
  • 5. Java SE 7 稿 草冊手術技 提示 在這個類別圖中,第一格中 Role 表示類別名稱,第二格中 name、 level 、 blood 表 示 資 料 成 員 , : 號 之 後 為 各 成 員 型 態 , - 號 表 示 private,第三格表示方法名稱,+號表示 public,:號之後表示傳回 型態,繼承則以空心箭頭表示。 Lab 如何看出確實有繼承了呢?以下簡單的程式可以看出: Game1 RPG.java package cc.openhome; public class RPG { public static void main(String[] args) { SwordsMan swordsMan = new SwordsMan(); swordsMan.setName("Justin"); swordsMan.setLevel(1); swordsMan.setBlood(200); System.out.printf(" :士劍 (%s, %d, %d)%n", swordsMan.getName(), swordsMan.getLevel(), swordsMan.getBlood()); Magician magician = new Magician(); magician.setName("Monica");
  • 6. Java SE 7 稿 草冊手術技 magician.setLevel(1); magician.setBlood(100); System.out.printf(" :師法魔 (%s, %d, %d)%n", magician.getName(), magician.getLevel(), magician.getBlood()); } } 雖然 SwordsMan 與 Magician 並沒有定義 getName()、getLevel()與 getBlood()等方法,但從 Role 繼承了這些方法,所以就如範例中可以直接使用, 執行的結果如下: :士劍 (Justin, 1, 200) :師法魔 (Monica, 1, 100) 繼承的好處之一,就是若你要將 name、level、blood 改名為其它名稱,那就 只要修改 Role.java 就可以了,只要是繼承 Role 的子類別都無需修改。 注意 有 的 書 籍 或 文 件 會 說 , private 成 員 無 法 繼 承 , 那 是 錯 的 ! 如 果 private 成員無法繼承,那為什麼上面的範例 name、level、blood 記錄的值會顯示出來呢?private 成員會被繼承,只不過子類別無法直 接存取,必須透過父類別提供的方法來存取(如果父類別願意提供存取 方法的話) 。 6.1.2 多型與 is-a 子類別只能繼承一個父類別,繼承除了可避免類別間重複的行為定 在 Java 中,子類別只能繼承一個父類別 子類別只能繼承一個父類別 義外,還有個重要的關係,那就是子類別與父類別間會有 is-a 的關係,中文稱為「是 「 一種」 一種」的關係,這是什麼意思?以先前範例來說,SwordsMan 繼承了 Role,所以 SwordsMan 是一種 Role(SwordsMan is a Role) ( ),Magician 繼承了 Role,所 以 Magician 是一種 Role(Magician is a Role) ( )。 為何要知道繼承時,父類別與子類別間會有「是一種」的關係?因為要開始理解 多型(Polymorphism) ,必須先知道你操作的物件是「哪一種」東西! 來看實際的例子,以下的程式碼片段,相信你現在沒有問題地看懂,而且知道可 以通過編譯: SwordsMan swordsMan = new SwordsMan(); Magician magician = new Magician(); 那你知道以下的程式片段也可以通過編譯嗎? Role role1 = new SwordsMan(); Role role2 = new Magician(); 那你知道以下的程式片段為何無法通過編譯呢? SwordsMan swordsMan = new Role(); Magician magician = new Role();
  • 7. Java SE 7 稿 草冊手術技 編譯器就是語法檢查器,要知道以上程式片段為何可以通過編譯,為何無法通過 編譯,就是將自己當作編譯器,檢查語法的邏輯是否正確,方式是從 號右邊往左讀: 方式是從=號右邊往左讀 方式是從 右邊是不是一種左邊呢(右邊型態是不是左邊型態的子類別)? 圖 5.1 運用 is a 關係判斷語法正確性 從右往左讀,SwordsMan 是不是一 種 Role 呢?是的 ! 所以編譯通過。 Magician 是不是一種 Role 呢?是的!所以編譯通過。同樣的判斷方式,可以知道 為何以下編譯失敗: SwordsMan swordsMan = new Role(); // Role 種一是不是 SwordsMan ? Magician magician = new Role(); // Role 種一是不是 Magician ? 編譯器認為第一行,Role 不一定是一種 SwrodsMan,所以編譯失敗,對於第 二行,編譯器認為 Role 不一定是一種 Magician,所以編譯失敗。繼續把自己當成 編譯器,再來看看以下的程式片段是否可以通過編譯: Role role1 = new SwordsMan(); SwordsMan swordsMan = role1; 這個程式片段最後會編譯失敗,先從第一行看,SwordsMan 是一種 Role,所 以這行可以通過編譯。編譯器檢查這類語法,一次只看一行,就第二行而言,編譯器 看到 role1 為 Role 宣告的名稱,於是檢查 Role 是不是一種 SwordsMan,答案是 不一定,所以編譯失敗在第二行! 編譯器會檢查父子類別間的「是一種」關係,如果你不想要編譯器囉嗦,可以叫 它住嘴: Role role1 = new SwordsMan(); SwordsMan swordsMan = (SwordsMan) role1; 對於第二行,原本編譯器想囉嗦地告訴你,Role 不一定是一種 SwordsMan, 但你加上了(SwordsMan)讓它住嘴了,因為這表示,你就是要讓 Role 扮演 CAST) ( ) SwrodsMan,既然你都明確要求編譯器別囉嗦了,編譯器就讓這段程式碼通過編譯 不過後果得自行負責! 了,不過後果得自行負責 不過後果得自行負責
  • 8. Java SE 7 稿 草冊手術技 以上面這個程式片段來說,role1 確實參考至 SwordsMan 實例,所以在第二行 扮演 讓 SwordsMan 實例扮演 SwordsMan 並沒有什麼問題,所以執行時期並不會出錯。 圖 5.2 判斷是否可扮演(CAST)成功 判斷是否可扮演( ) 但是以下的程式片段,編譯可以成功,但執行時期會出錯: Role role2 = new Magician(); SwordsMan swordsMan = (SwordsMan) role2; 對於第一行,Magician 是一種 Role,可以通過編譯,對於第二行,role2 為 Role 型態,編譯器原本認定 Role 不一定是一種 SwordsMan 而想要囉嗦,但是你 明確告訴編譯器,就是要讓 Role 扮演為 SwordsMan,所以編譯器就讓你通過編譯 了,不過後果自負,實際上,role2 參考的是 Magician,你要讓魔法師假扮為劍 士?這在執行上會是個錯誤,JVM 會拋出 java.lang.ClassCastException。 扮演( )失敗,執行時拋出 圖 5.3 扮演(CAST)失敗,執行時拋出 ClassCastException 使用有一種(is-a)原則,你就可以判斷,何時編譯成功,何時編譯失敗,以及
  • 9. Java SE 7 稿 草冊手術技 將扮演(CAST)看作是叫編譯器住嘴語法,並留意參考的物件實際型態,你就可以 判斷何時扮演成功,何時會拋出 ClassCastException。例如以下編譯成功,執行 也沒問題: SwordsMan swordsMan = new SwordsMan(); Role role = swordsMan; // SwordsMan 種一是 Role 以下程式片段會編譯失敗: SwordsMan swordsMan = new SwordsMan(); Role role = swordsMan; 種一是 // SwordsMan Role 譯編 過通行 這, SwordsMan swordsMan = role; // Role 種一是定一不 敗失譯 編, SwordsMan 以下程式片段編譯成功,執行時也沒問題: SwordsMan swordsMan = new SwordsMan(); Role role = swordsMan; // SwordsMan 種一是 Role 譯 編過 通行這 , // 讓要器 譯編訴告你 Role 演扮 SwordsMan 譯編 過通行這 下以, SwordsMan swordsMan = (SwordsMan) role; 考參 // role SwordsMan 功成行 執,例實 以下程式片段編譯成功,但執行時拋出 ClassCastException: SwordsMan swordsMan = new SwordsMan(); Role role = swordsMan; // SwordsMan 種一是 Role 譯編 過通行 這, // 讓要器 譯編訴告你 Role 演扮 Magician 譯 編過通行 這 下以, 考參 Magician magician = (Magician) role; // role SwordsMan 敗失 行執,例 實 經過以上這一連串的語法測試,好像只是在玩弄語法?不!你懂不懂以上這些東 西,牽涉到寫出來的東西有沒有彈性、好不好維護的問題! 有這麼嚴重嗎?來出個題目給你吧!請設計 static 方法,顯示所有角色的血 量!OK!上一章剛學過如何定義方法,有的人會撰寫以下的方法定義: public static void showBlood(SwordsMan swordsMan) { System.out.printf("%s 量血 %d%n", swordsMan.getName(), swordsMan.getBlood()); } public static void showBlood(Magician magician) { System.out.printf("%s 量血 %d%n", magician.getName(), magician.getBlood()); } 分別為 SwordsMan 與 Magician 設計 showBlood()同名方法,這是重載方法 的運用,如此就可以如下呼叫: showBlood(swordsMan); // swordsMan 是 態型 SwordsMan showBlood(magician); // magician 是Magician 態型 現在的問題是,目前你的遊戲中是只有 SwordsMan 與 Magician 兩個角色,如 果有一百個角色呢?重載出一百個方法?這種方式顯然不可能!如果所有角色都是 繼承自 Role,而且你知道這些角色都是一種 Role,你就可以如下設計方法並呼叫:
  • 10. Java SE 7 稿 草冊手術技 Game2 RPG.java package cc.openhome; 宣告為 Role 型態 public class RPG { public static void showBlood(Role role) { System.out.printf("%s 量血 %d%n", role.getName(), role.getBlood()); } public static void main(String[] args) { SwordsMan swordsMan = new SwordsMan(); swordsMan.setName("Justin"); swordsMan.setLevel(1); swordsMan.setBlood(200); Magician magician = new Magician(); magician.setName("Monica"); magician.setLevel(1); magician.setBlood(100); showBlood(swordsMan); SwordsMan 是一種 Role showBlood(magician); Magician 是一種 Role } } 在這邊僅定義了一個 showBlood()方法,參數宣告為 Role 型態 ,第一次呼 叫 showBlood()時傳入了 SwordsMan 實例,這是合法的語法,因為 SwordsMan 是一種 Role ,第一次呼叫 showBlood()時傳入了 Magician 實例也是可行,因 為 Magician 是一種 Role 。執行的結果如下: Justin 量血 200 Monica 量血 100 這樣的寫法好處為何?就算有 100 種角色,只要它們都是繼承 Role,都可以使 用這個方法顯示角色的血量,而不需要像先前重載的方式,為不同角色寫 100 個方 法,多型的寫法顯然具有更高的可維護性。 使用單一介面操作多種型態的物件!若用以 使用單一介面操作多種型態的物件 什麼叫多型?以抽象講法解釋,就是使用單一介面操作多種型態的物件 上的範例來理解,在 showBlood()方法中,既可以透過 Role 型態操作 SwordsMan 物件,也可以透過 Role 型態操作 Magician 物件。 注意 稍後會學到 Java 中 interface 的使用,在多型定義中,使用單一介面 操作多種型態的物件,這邊的介面並不是專指 Java 中的 interface,
  • 11. Java SE 7 稿 草冊手術技 而是指物件上可操作的方法。 重新定義行為 6.1.3 重新定義行為 現在有個需求,請設計 static 方法,可以播放角色攻擊動畫,你也許會這麼想, 學剛剛多型的寫法,設計個 drawFight()方法如何? 圖 5.4 Role 沒有定義 fight()方法 方法 對 drawFight()方法而言,只知道傳進來的會是一種 Role 物件,所以編譯器 也只能檢查你呼叫的方法,Role 是不是有定義,顯然地,Role 目前並沒有定義 fight()方法,因此編譯錯誤了。 然而你仔細觀察一下 SwordsMan 與 Magician 的 fight()方法,他們的方法 方法 簽署( 簽署(method signature)都是: ) public void fight() 也就是說,操作介面是相同的,只是方法實作內容不同,你可以將 fight()方 Lab 法提昇至 Role 類別中定義: Game3 Role.java package cc.openhome; public class Role { ... 略 public void fight() { // 義定新重要別類子 義定新重要別類子 義定新重要別類子 義定新重要別類子 為行際實的 為行際實的 為行際實的 fight() 為行際實的 } } 在 Role 類別中定義了 fight()方法,由於實際上角色如何攻擊,只有子類別 才知道,所以這邊的 fight()方法內容是空的,沒有任何程式碼實作。SwordsMan 繼承 Role 之後,再對 fight()的行為進行定義: Game3 SwordsMan.java package cc.openhome; public class SwordsMan extends Role {
  • 12. Java SE 7 稿 草冊手術技 public void fight() { System.out.println(" 擊攻劍揮 擊攻劍揮 擊攻劍揮 擊攻劍揮"); } } 在繼承父類別之後,定義與父類別中相同的方法簽署,但實作內容不同,這稱為 重新定義( 重新定義(Override) ),因為對父類別中已定義的方法實作不滿意,所以你在子類別 中重新定義實作。Magician 繼承 Role 之後,也重新定義了 fight()的行為: Game3 Magician.java package cc.openhome; public class Magician extends Role { public void fight() { System.out.println(" 擊攻法魔 擊攻法魔 擊攻法魔 擊攻法魔"); } ... 略 } 由於 Role 現在定義了 fight()方法(雖然方法區塊中沒有程式碼實作) ,所以 編譯器不會找不到 Role 的 fight()了,因此你可以如下撰寫: Game3 RPG.java package cc.openhome; 宣告為 Role 型態 public class RPG { public static void drawFight(Role role) { System.out.print(role.getName()); role.fight(); } public static void main(String[] args) { SwordsMan swordsMan = new SwordsMan(); swordsMan.setName("Justin"); swordsMan.setLevel(1); swordsMan.setBlood(200); Magician magician = new Magician(); magician.setName("Monica"); magician.setLevel(1); magician.setBlood(100);
  • 13. Java SE 7 稿 草冊手術技 drawFight(swordsMan); 實際操作的是 SwordsMan 實例 drawFight(magician); 實際操作的是 Magician 實例 } } 在 fight()方法宣告了 Role 型態的參數 ,那方法中呼叫的,到底是 Role 中 定義的 fight(),還是個別子類別中定義的 fight()呢?如果傳入 fight()的是 SwordsMan,role 參數參考的就是 SwordsMan 實例,操作的就是 SwordsMan 上 的方法定義: 圖 5.5 role 牌子掛在 SwordsMan 實例 這就好比 role 牌子掛在 SwordsMan 實例身上,你要求有 role 牌子的物件攻 擊,發動攻擊的物件就是 SwordsMan 實例。同樣地,如果傳入 fight()的是 Magician,role 參數參考的就是 Magician 實例,操作的就是 Magician 上的方 法定義: 圖 5.6 role 牌子掛在 Magician 實例 所以範例最後的執行結果是: Justin 擊攻劍揮 Monica 擊攻法魔 在重新定義父類別中某個方法時,子類別必須撰寫與父類別方法中相同的簽署, 然而如果疏忽打錯字了: public class SwordsMan extends Role {
  • 14. Java SE 7 稿 草冊手術技 public void Fight() { System.out.println(" 擊攻劍揮 "); } } 以這邊的例子來說,父類別中定義的是 fight(),但子類別中定義了 Fight(), 這就不是重新定義 fight()了,而是子類別新定義了一個 Fight()方法,這是合法 的方法定義,編譯器並不會發出任何錯誤訊息,你只會在運行範例時,發現為什麼 SwordsMan 完全沒有攻擊。 在 JDK5 之 後 支 援 標 註 ( Annotation ) 其 中 一 個 內 建 的 標 準 標 註 就 是 , @Override,如果你在子類別中某個方法前標註@Override,表示要求編譯器檢 查,該方法是不是真的重新定義了父類別中某個方法,如果不是的話,就會引發編譯 錯誤。例如: 圖 5.7 編譯器檢查是否真的重新定義父類別某方法 編譯器檢查是否真的重新定義父類別某方法 如果你要重新定義某方法,加上@Override,就不用擔心打錯字的問題了。關 於標註詳細語法,會在之後章節說明。 抽象方法、 6.1.4 抽象方法、抽象類別 上一個範例中 Role 類別的定義中,fight()方法區塊中實際上沒有撰寫任何程 式碼,雖然滿足了多型需求,但會引發的問題是,你沒有任何方式強迫或提示子類別 一定要實作 fight()方法,只能口頭或在文件上告知,不過如果有人沒有傳達到、 沒有看文件或文件看漏了呢? 如果某方法區塊中真的沒有任何程式碼實作,可以使用 abstract 標示該方法 Lab 為抽象方法(Abstract method) 抽象方法( 抽象方法 ,該方法不用撰寫{}區塊,直接;結束即可。例如: ) Game4 Role.java package cc.openhome; public abstract class Role { ... 略
  • 15. Java SE 7 稿 草冊手術技 public abstract void fight(); } 類別中若有方法沒有實作,並且標示為 abstract,表示這個類別定義不完整, 定義不完整的類別就不能用來生成實例 就不能用來生成實例,這就好比設計圖不完整,不能用來生產成品 定義不完整的類別就不能用來生成實例 中規定內含抽象方法的類別 的類別, 一樣。Java 中規定內含抽象方法的類別,一定要在 class 前標示 abstract,如上 抽象類別( 例所示,這表示這是一個定義不完整的抽象類別(Abstract class) 抽象類別 )。如果嘗試用抽 象類別建構實例,就會引發編譯錯誤: 圖 5.8 不能實例化抽象類別 子類別如果繼承抽象類別,對於抽象方法有兩種作法,一種作法是繼續標示該方 法為 abstract (該子類別因此也是個抽象類別 必須在 class 前標示 abstract) , , 另一個作法就是實作抽象方法。如果兩個作法都沒實施,就會引發編譯錯誤: 圖 5.9 沒有實作抽象方法 6.2 繼承語法細節 上一節介紹了繼承的基礎觀念與語法,然而結合 Java 的特性,繼承還有許多細 節必須明瞭 像是哪些成員可以限定在子類別中使用 哪些方法簽署算重新定義 Java , 、 、 中所有物件都是一種 java.lang.Object 等細節,這將於本節中詳細說明。 6.2.1protected 成員 就上一節的 RPG 遊戲來說,如果建立了一個角色,想顯示角色的細節,必須如 下撰寫: SwordsMan swordsMan = new SwordsMan(); ... 略 System.out.printf(" 士劍 士劍 士劍 士劍 (%s, %d, %d)%n", swordsMan.getName(), swordsMan.getLevel(), swordsMan.getBlood()); Magician magician = new Magician(); ... 略 System.out.printf(" 師法魔 師法魔 師法魔 師法魔 (%s, %d, %d)%n", magician.getName(),