Inheritance[ɪnˋhɛrɪtəns]
先看下面的代碼
class Pokemon{ private float weight; private float height; } class Pikachi{ private float weight; private float height; }
當Pokemon需要有weight及height二個屬性時, 而皮卡丘(Pikachi)也同樣需要這二個屬性, 這造成了程式碼的重複撰寫
為了避免這種重複現像, 就可使用繼承的方式, 把共通方法及屬性集中在父類別, 再由子類別繼承
class Pokemon{
private float weight;
private float height;
}
class Pikachi extends Pokemon{
}
有了繼承的機制, 只要extends一個字就全交由電腦copy屬性及方法. 這時再也不怕神奇寶貝到底有幾千種.
子類別宣告的語法如下
[modifier] class class_identifier extends superclass_identifier, 如
class Pikachi extends Pokemon{}
繼承驗証
class Pokemon{ public int speed; private int level; public void setLevel(int level){ this.level=level; } public int getLevel(){ return level; } } class Pikachu extends Pokemon{} public class Test{ public static void main(String[] args){ Pikachu p1=new Pikachu(); p1.speed=100; //此行ok //p1.level=10; 此行error } }
Pikachu繼承了Pokemon後, 在Pikachu內並沒有寫任何的程式碼. 而在main()裏新增 Pikachu p1=new Pikachu(). 此時 p1.speed=100並不會出錯, 表示speed的確有從父類別繼承到子類別.
但p1.level=100, 這一行是會出錯的. 那表示private物件變數不會被繼承嗎?
網路上的標準答案是 : private物件變數不會被繼承
變數遮蔽(Shadow)
接下來看下面的代碼
public class Test { public static void main(String[] args){ Pikachu p1=new Pikachu(); p1.level=100; p1.setLevel(500); System.out.printf("p1.level = %d\n", p1.level); System.out.printf("p1.getLevel = %d\n", p1.getLevel()); } } class Pokemon{ public int speed; private int level; public void setLevel(int level){ this.level=level; } public int getLevel(){ return level; } } class Pikachu extends Pokemon{ public int level; } 結果 : p1.level = 100 p1.getLevel = 500
當子類別也有 int level時, 此時稱為變數遮蔽. 為了方便測試, 我們把子類別的level改為public. 直接印出子類別的 level, 與在子類別使用 getLevel(), 有著不一樣的值. 所以這就表示private 變數應該也有被繼承, 只是需要父類別的key才有辦法打開, 如下圖所示
當子類別物件使用父類別參考時, 調用遮蔽的屬性時會使用父類別中定義的屬性
方法覆蓋(Overriding)
若父類別的方法於子類別沒有被重寫一次, 則子類別的物件將執行父類別的方法.
若父類別的方法於子類別重新再實作一次, 稱為覆蓋.
子類別覆蓋父類別方法後, 父類別方法將屍骨無存. 就算子類別物件參考了父類別, 一樣看不見父類別方法, 只有執行子類別的方法留著.
現在問題來了, 父類別的level是private, 需仰仗父類別的public 方法存取. 但父類別方法都屍骨無存了, 而子類別新創的方法根本無法存取 level, 該如何是好. 此時只能在子類別方法中使用觀落音法 – super.setLevel(10).
public class Test { public static void main(String[] args) { Pokemon p1=new Pikachu(); p1.setLevel(10); } } class Pokemon{ private int level; public void setLevel(int level){ this.level=level; } public int getLevel(){ return level; } } class Pikachu extends Pokemon{ public void setLevel(int level){ //this.level=level; 此行error super.setLevel(level);//觀落音法, 執行父類別的 setLevel(10) } }
this是本類別(林北)的意思, 當物件變數與區域變數同名稱時就發揮作用了. 如this.level=level; this.level是指物件變數, 而只有level時是指區域變數.
當子類別物件使用父類別參考時, 調用覆寫的方法時會呼叫子類別中定義的方法
建構子覆蓋
因為父子類別的建構子名稱都不一樣, 所以建構子並沒有覆蓋的問題.
不過有個靈異現象, 當new Pikachu()時, 不但會印出皮卡丘出生了, 詭異的是最前面還會先印Pokemon出生了. 怪了, new 皮卡丘時, 干Pokemon啥屁事呢.
原來, 系統會在子類別建構子第一行自動加入 super() , 所以每當要執行子類別建構子前, 會先執行父類別建構子.
public class Test { public static void main(String[] args) { Pikachu p1=new Pikachu(); } } class Pokemon extends Object{ //extends Object系統會自動加入 public Pokemon(){ System.out.println("Pokemon出生了"); } } class Pikachu extends Pokemon{ public Pikachu(){ super(); //系統自動加的 System.out.println("皮卡丘出生了"); } } 結果 : Pokemon出生了 皮卡丘出生了
那麼, class Pokemon{}並沒有父類別, 該不會在Pokemon建構子自動加super()吧. 答案是~~會的, 因為calss Pokemon沒父類別, 系統則會自動加入 class Pokemon extends Object{}
所以, Object才是所有類別的頂端父類別. 只有Object才沒有父類別.
方法覆蓋的存取權限
子類別覆蓋父類別方法時,存取層級必需大於或等於父類別, 比如父類別是private, 則子類別必需是private, protected 或public
方法覆蓋的回傳值
方法覆蓋後, 方法傳回值的型態必須相同或是原方法傳回值型別的子類別
class Skill{} class AdvSkill extends Skill{} class Pokemon{ public Skill getDetail(){ System.out.print("這是Pokemon, "); return new Skill(); } } class Pikachi extends Pokemon{ public AdvSkill getDetail(){ super.getDetail(); System.out.println("這是皮卡丘"); return new AdvSkill(); } }
覆蓋與Exception
覆蓋的方法若有Exception修飾子, 則子類別的例外可以不寫, 或是跟父類別的例外相同, 或是例外的子類別
覆蓋Object方法
在Java中每個類別都是繼承Object類別
public class Employee{} 同public class Employee extends Object{}
也就是說, 編譯器會自動加為extends Object這個字.
需考慮覆蓋Object的三個方法就是toString(), equals(), hashCode();
Object toString 方法
Employee e=new Employee();
System.out.println(e); <==取得e.toString()的返回值
所以需要覆蓋toString如下
public String toString(){return “Pokemon”;}
Object equals方法
Object的equals預設是在比對二個物件是否為同一個, 比如
Employee x=new Employee();
Employee y=new Employee();
則x.equals(y);<==false, 也就是說Object 的equals骨子裏就是在作
if (x==y)reutrn true;
else return false;
所以如果要比對二個物件裏的內容時, 就需覆蓋equals方法
public boolean equlas(Object o){…..}
String 類別的equals就是作過覆蓋的, 所以才能簡查出字串的值是否相同
Object hashCode方法
一般的規定中, 如果equals 為true, 則二物件返回的hashcode必需是相同的
在重新定義 equals
時,最好重新一併重新定義 hashCode
。只是 hashCode
該怎麼算呢?算出來的雜湊碼最好是儘量別重複,以免引起雜湊碰撞(Hash collision),過多的雜湊碰撞可能會有效能問題,甚至增加 hash collision dos 的可能性。
Netbeans會自動產生hashCode()的方法. 在equals的方法點一下, 再按下Alt-Enter/Generate missing hashCode();
父類別自訂建構子的地雷
看倌們看到現在是不是有種~~有完沒完啊, 這麼複雜!! 是的, 的確不好懂, 但請忍耐且靜下心來, 本人寫這些文章, 更煩啊.
class Pokemon{ private int level; public Pokemon(int level){ this.level=level; System.out.println("Pokemon出生了"); } } class Pikachu extends Pokemon{ public Pikachu(){ //此行會出錯 super(); //此行系統自動加入 System.out.println("皮卡丘出生了"); } }
系統會於子類別建構子自動加入super()去執行父類別預設建構子, 但當父類別只有自訂建構子時, 那不就掛了, 如上面紅色的地方就會出錯.
解決此地雷的方法有
1. 於父類別手動加入預設建構子
2. 於子類別建構子手動加入super(xxx)
class Pokemon{
private int level;
public Pokemon(int level){
this.level=level;
System.out.println("Pokemon出生了");
}
}
class Pikachu extends Pokemon{
public Pikachu(){
super(10); //手動加入
System.out.println("皮卡丘出生了");
}
}
建構子總結
1. new 出物件時, 會自動執行的方法, 叫建構子Constructor.
2. 建構子名稱需與類別名稱一模一樣.
3. 建構子就是一個特殊的方法, 但沒有傳回值, 連void也沒有.
4. 若沒有預設建構子, 系統會自動加入建構子.
5. 若有自訂建構子, 系統就不會自動加入預設建構子.
6. 系統會於子類別建構子第一行加入super()
7. 父類別若沒有繼承其他類別, 系統會自動加入 extends Object
8. 父類別若只有自訂建構子, 子類別自動加入 super(), 就會找不到父類別的預設建構子.
覆蓋總整理
覆蓋發生在繼承的關係中
覆蓋的方法名稱必須相同
方法傳回值, 回傳型態必須相同或是原方法傳回值型別的子類別
方法中的參數, 不論是數量還是型態, 都必須一模一樣
存取權限不可小於原方法
若有例外, 子類別的方法可以不拋出, 亦可拋出與原方法相同的例外, 或原方法例外事件類別的子類別
I enjoy the way your writing reflects your unique character. It’s like engaging in a meaningful conversation.