繼承基本上就是避免多個類別間重複定義了相同的實作。以實際的例子來說明比較清楚,假設你在正開發一款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) {
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;
}
}
你注意到什麼呢?因為只要是遊戲中的角色,都會具有角色名稱、等級與血量,類別中也都為名稱、等級與血量定義了取值方法與設值方法,Magician
中粗體字部份與SwordsMan
中相對應的程式碼重複了。重複在程式設計上,就是不好的訊號。舉個例子來說,如果你要將name
、level
、blood
改名為其他名稱,那就要修改SwordsMan
與Magician
兩個類別,如果有更多類別具有重複的程式碼,那就要修改更多類別,造成維護上的不便。
如果要改進,可以把相同的程式碼提昇(Pull up)為父類別:
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;
}
}
這個類別在定義上沒什麼特別的新語法,只不過是將SwordsMan
與Magician
中重複的程式碼複製過來。接著SwordsMan
可以如下繼承Role
:
package cc.openhome;
public class SwordsMan extends Role {
public void fight() {
System.out.println("揮劍攻擊");
}
}
在這邊看到了新的關鍵字extends
,這表示SwordsMan
會擴充Role
的實作,也就是繼承Role
的實作,再擴充Role
原本沒有的fight()
實作。程式面上來說,Role
中有定義的程式碼,SwordsMan
中都繼承而擁有了,並再定義了fight()
方法的程式碼。類似地,Magician
也可以如下定義繼承Role
類別:
package cc.openhome;
public class Magician extends Role {
public void fight() {
System.out.println("魔法攻擊");
}
public void cure() {
System.out.println("魔法治療");
}
}
Magician
繼承Role
的實作,再擴充了Role
原本沒有的fight()
與cure()
實作。
如何看出確實有繼承了呢?以下簡單的程式可以看出:
package cc.openhome;
public class RPG {
public static void main(String[] args) {
demoSwordsMan();
demoMagician();
}
static void demoSwordsMan() {
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());
}
static void demoMagician() {
Magician magician = new Magician();
magician.setName("Monica");
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
繼承了這些方法,所以就如範例中可以直接使用,執行的結果如下:
魔法師:(Monica, 1, 100)
繼承的好處之一,就是若你要將
name
、level
、blood
改名為其它名稱,那就只要修改Role.java就可以了,只要是繼承Role
的子類別都無需修改。有的書籍或文件會說,private
成員無法繼承,那是錯的!如果private
成員無法繼承,那為什麼上面的範例name
、level
、blood
記錄的值會顯示出來呢?private
成員會被繼承,只不過子類別無法直接存取,必須透過父類別提供的方法來存取(如果父類別願意提供存取方法的話)。