Ch06 抽象類別和多形 物件導向程式設計(2)
方法的覆蓋(1/4) 子類別的重新定義物件方法稱為方法的覆蓋(Overriding)。 方法覆蓋時,下列各項都必須和父類別定義的方法相同: 方法的回傳型別。 方法名稱。 形式參數列中的型別及順序。 如果子類別定義的方法和父類別定義的方法同名,但參數不同時,只能算是一種多載(overloading)
方法的覆蓋(2/4) 覆蓋的原理:先碰到子類別定義之成員
方法的覆蓋(3/4) 方法覆蓋的主要用意是「修改原有功能的實作」。 使用 final 宣告的父類別之物件方法不能被覆蓋。 靜態方法無法使用覆蓋(沒有覆蓋的特性)。 覆蓋舊方法時,新定義的方法可以透過 super 參照呼叫舊方法。
方法的覆蓋(4/4) 方法覆蓋要注意下列幾點: 方法覆蓋時,沒有領域不同的區別,所有被覆蓋的方法一律打入冷宮。 回傳型別必須相同。 不能縮減方法的存取權限 丟出的例外型別必須相同 方法覆蓋時,沒有領域不同的區別,所有被覆蓋的方法一律打入冷宮。
範例1: class Ch05_01 { public static void main(String [] args) { Super05_01 a = new Super05_01(); Sub05_01 b = new Sub05_01(); System.out.println("a的data: " + a.showData()); System.out.println("b的data: " + b.showData()); } class Super05_01 {String data = "Super05_01原始資料"; String showData() { return data; } class Sub05_01 extends Super05_01 { String data = "Sub05_01的原始資料"; { return data; }
範例2:方法覆蓋時,沒有領域不同的區別,所有被覆蓋的方法一律打入冷宮 class Ch05_02 { public static void main(String [] args) { Super05_02 a = new Super05_02(); Sub05_02 b = new Sub05_02(); a.showData(); b.showData(); } class Super05_02 { String data = "Super05_02原始資料"; void showData() { System.out.println(getData()); } String getData() { return data; } class Sub05_02 extends Super05_02 { String data = "Sub05_02的原始資料"; { return data; }
範例3:使用super呼叫父類別方法 class Ch05_03 { public static void main(String [] args) Super05_03 a = new Super05_03(); Sub05_03 b = new Sub05_03(); System.out.println("a的data: " + a.showData()); System.out.println("b的data: " + b.showData()); } class Super05_03 String data = "Super05_03原始資料"; String showData() { return data; } class Sub05_03 extends Super05_03 { String data = "Sub05_03的原始資料"; { return super.showData(); }
抽象類別(1/3) 類別定義中,只要有一個方法(或以上)定義為抽象方法(abstract method),則該類別為抽象類別(abstract class)。 抽象方法並沒有定義方法的主體(沒有實作) 無法利用抽象類別建立物件。 抽象類別的用處是當作父類別,讓子類別繼承。 子類別必須將父類別中的抽象方法實作出來,才能建立物件。
抽象類別(2/3) 抽象類別的定義語法: 某個類別的主體內,宣告了抽象方法,則該類別必須宣告為抽象類別。 修飾字 abstract class 類別名稱 { //屬性宣告 修飾字 abstract 型別 方法名稱(參數列); //其它方法定義 } 某個類別的主體內,宣告了抽象方法,則該類別必須宣告為抽象類別。 抽象類別雖然不能用以建立物件,但是可以當作物件的型別,用來宣告參照變數。 抽象類別的子類別若不完全實作抽象方法,則依然是抽象類別。
抽象類別(3/3) 使用抽象類別的用意,主要是「制定固定的訊息接收管道,但不把焦點放在訊息的處理上」。 抽象類別和一般類別一樣,都可以定義建構子,只是不能直接以抽象類別的建構子建立物件。 抽象方法的目的就是為了讓子類別實作,所以abstract 不能同時和 final、static 或 private 一起使用。
多型(polymorphism) 不論參照的型別為何,呼叫方法時,呼叫最新的覆蓋方法。 多型指的是「使用相同的訊息呼叫,可以進行不同的功能操作」。 動態結合(dynamic binding):當某個物件方法,接收傳入的A類別之物件時,該方法無法得知傳入的是A類別的物件,或A之延伸類別的物件,直到執行時才會知道,也才能知道要呼叫的是哪個方法。
多型(polymorphism) vs. 多載(overload)(1/3) 多載: class 液體_05 { String drink; 液體_05(String drink_name) { drink = drink_name; } } class 固體_05 { String food; 固體_05(String food_name) { food = food_name; } class 動物_05 { void 吃(液體_05 a) { System.out.println(a.drink + "是用喝的或吸的!"); } void 吃(固體_05 a) { System.out.println(a.food + "是要用咬的或磨的!"); } class Ch05_05 { public static void main(String [] args) { 液體_05 x1 = new 液體_05("珍珠奶茶"); 固體_05 x2 = new 固體_05("漢堡"); 液體_05 x3 = new 液體_05("水"); 固體_05 x4 = new 固體_05("草"); 動物_05 牛 = new 動物_05(); 動物_05 人 = new 動物_05(); System.out.println("== 牛 =="); 牛.吃(x3); 牛.吃(x4); System.out.println("== 人 =="); 人.吃(x1); 人.吃(x2); } Ch05_05.java
多型(polymorphism) vs. 多載(overload)(2/3) class 動物_06 { String kind; void 吃(液體_06 a) { System.out.println(a.drink + "是用喝的或吸的!"); } void 吃(固體_06 a) { System.out.println(a.food + "是要用咬的或磨的!"); } } class 人_06 extends 動物_06 { 人_06() { kind = "人"; } { System.out.println(a.drink + "是用喝的!"); } { System.out.println(a.food + "是用咬的!"); } class 牛_06 extends 動物_06 { 牛_06() { kind = "牛"; } { System.out.println(a.drink + "是用吸的!"); } { System.out.println(a.food + "是用磨的!"); } Ch05_06.java
多型(polymorphism) vs. 多載(overload)(3/3) class Ch05_06 { public static void main(String [] args) { 液體_06 x1 = new 液體_06("珍珠奶茶"); 固體_06 x2 = new 固體_06("漢堡"); 液體_06 x3 = new 液體_06("水"); 固體_06 x4 = new 固體_06("草"); 牛_06 y1 = new 牛_06(); 人_06 y2 = new 人_06(); System.out.println("== " + y1.kind + " =="); y1.吃(x3); y1.吃(x4); System.out.println("== " + y2.kind + " =="); y2.吃(x1); y2.吃(x2); } class 液體_06 { String drink; 液體_06(String drink_name) { drink = drink_name; } } class 固體_06 { String food; 固體_06(String food_name) { food = food_name; }
物件的型別轉換 子類別物件也屬於父類別物件 小紅是一條魚。 小紅是一隻脊椎動物。 小紅是一隻動物。
範例4: class Ch05_07 { public static void main(String [] args) 液體_07 x1 = new 液體_07("珍珠奶茶"); 固體_07 x2 = new 固體_07("漢堡"); 液體_07 x3 = new 液體_07("水"); 固體_07 x4 = new 固體_07("草"); 動物_07 y1 = new 牛_07(); 動物_07 y2 = new 人_07(); System.out.println("== " + y1.kind + " =="); y1.吃(x3); y1.吃(x4); System.out.println("== " + y2.kind + " =="); y2.吃(x1); y2.吃(x2); } Ch05_07.java
物件的型別轉換- 異質集合 相同類別的物件集合稱為同質集合(homogenous collections)。 不同類別的物件集合稱為異質集合(heterogeneous collections)。 基本型別的陣列為同質集合。 當父類別型別的參照陣列之元素分別指向不同的子類別物件時,此參照陣列稱為異質集合。 異質集合建立在父類別型別參照變數可以指向子類別物件的原理上。
物件的型別轉換- 異質集合的物件陣列 異質集合的物件陣列
範例5 class Ch05_08 { public static void main(String [] args) 液體_08 x1 = new 液體_08("珍珠奶茶"); 固體_08 x2 = new 固體_08("漢堡"); 液體_08 x3 = new 液體_08("水"); 固體_08 x4 = new 固體_08("草"); 動物_08 y[] = {new 牛_08(), new 人_08()}; int i; for(i= 0; i<2; i++) System.out.println("== " + y[i].kind + " =="); y[i].吃(x1); y[i].吃(x2); y[i].吃(x3); y[i].吃(x4); }
物件的強制型別轉換(1/3) 物件型別轉換,其實是針對指向物件的參照變數,而不是針對物件實體。物件實體只要被建立,其佔用的記憶體位置及大小都不會被變更。 物件的強制型別轉換之語法 (目的類別)物件參照變數 物件參照變數所指向的物件實體,必須是目的類別的物件實體,或是其子類別的物件實體。
物件的強制型別轉換(2/3) 子類別物件轉換成父類別物件
物件的強制型別轉換(3/3) 父類別物件不具備子類別定義之成員,無法轉換成子類別物件。
instanceof運算子 instanceof運算式會得到一個布林值,若物件名稱所指向的物件實體屬於欲判斷的類別則回傳true;反之,回傳false。 物件名稱 instanceof 類別名稱 物件名稱所屬的參照型別必須和類別名稱有繼承關係,否則會發生錯誤。 instanceof的優先權和關係運算子的 <、>、<=、>= 相同,它們的運算結果都是布林值。
動腦動手時間(1) 請以範例4,5,6,7,8為參考 建立一個程式: 人和牛要吃所有的食物 食物類別:食物名稱 動物類別:吃() 液體類別 固體類別 動物類別:吃() 人類別:吃(固體), 吃(液體) 牛類別:吃(固體), 吃(液體) 人和牛要吃所有的食物
動腦動手時間(2) 考慮一商店買賣系統 商店有貨品: 執行下列動作: 輸出: 湯瑪士:進貨50,單價290 培西:進貨40,單價290 哈諾:進貨20,單價290 執行下列動作: 賣(湯瑪士, 10) 賣(培西, 5) 特價(哈諾, 190) 賣(湯瑪士,60) 賣(哈諾, 10) 輸出: 每次賣東西後,都要針對該筆交易產生帳單 每次賣東西後,要顯示現在存貨 每次有更新,也要顯示狀況 最後,顯示庫存及單價
abstract class 食物_ex1 { String food_name; abstract void 吃的方式(); } class 液體_ex1 extends 食物_ex1 液體_ex1(String drink_name) food_name = drink_name; void 吃的方式() System.out.println("喝的"); class 固體_ex1 extends 食物_ex1 固體_ex1(String food) food_name = food; System.out.println("嚼的");
abstract class 動物_ex1 { String kind; abstract void 吃(食物_ex1 a); } class 人_ex1 extends 動物_ex1 人_ex1() kind = "人"; void 吃(食物_ex1 a) if (a instanceof 液體_ex1) System.out.print(a.food_name + "是用杯子"); a.吃的方式(); else System.out.print(a.food_name + "是用牙齒"); class 牛_ex1 extends 動物_ex1 牛_ex1() kind = "牛"; System.out.print(a.food_name + "是用舌頭"); System.out.print(a.food_name + "是用臼齒");
class Ex05_01 { public static void main(String [] args) 食物_ex1 x[] = {new 液體_ex1("珍珠奶茶"), new 固體_ex1("漢堡"), new 液體_ex1("水"), new 固體_ex1("草")}; 動物_ex1 y[] = {new 牛_ex1(), new 人_ex1()}; int i, j; for(i= 0; i<2; i++) System.out.println("== " + y[i].kind + " =="); for(j = 0; j < 4; j++) y[i].吃(x[j]); }
薪資系統(1/8) abstract class 員工 { String 姓名; String 工作部門; int 薪水; 員工(String name, String dept) 姓名 = name; 工作部門 = dept; } void print薪資單() System.out.println("薪資單"); System.out.println("姓名: " + 姓名 + " 工作部門: " + 工作部門); abstract void 薪水();
薪資系統(2/8) abstract class 正職 extends 員工 { int 月薪; 正職(String name, String dept, int salary) super(name, dept); 月薪 = salary; } void print薪資單() super.print薪資單(); System.out.println("月薪: " + 月薪);
薪資系統(3/8) abstract class 兼職 extends 員工 { int 工資; 兼職(String name, String dept) super(name, dept); } void print薪資單() super.print薪資單(); System.out.println("工資: " + 工資);
薪資系統(4/8) class 主管 extends 正職 { 主管(String name, String dept, int salary) super(name, dept, salary); } void print薪資單() super.print薪資單(); System.out.println("總計: " + 薪水); void 薪水() 薪水 = 月薪;
薪資系統(5/8) class 一般員工 extends 正職 { int 加班費; 一般員工(String name, String dept, int salary, int h) super(name, dept, salary); 加班費 = h * 100; } void print薪資單() super.print薪資單(); System.out.println("加班費: " + 加班費); System.out.println("總計: " + 薪水); void 薪水() 薪水 = 月薪 + 加班費;
薪資系統(6/8) class 業務員 extends 正職 { int 業績獎金; 業務員(String name, String dept, int salary, int 業績) super(name, dept, salary); 業績獎金 = 業績 * 5 / 100; } void print薪資單() super.print薪資單(); System.out.println("業績獎金: " + 業績獎金); System.out.println("總計: " + 薪水); void 薪水() 薪水 = 月薪 + 業績獎金;
薪資系統(7/8) class 計件工作人員 extends 兼職 { 計件工作人員(String name, String dept, int 件數) super(name, dept); 工資 = 件數 * 10; } void print薪資單() super.print薪資單(); System.out.println("總計: " + 薪水); void 薪水() 薪水 = 工資;
薪資系統(8/8) class 計時工作人員 extends 兼職 { 計時工作人員(String name, String dept, int h) super(name, dept); 工資 = h * 100; } void print薪資單() super.print薪資單(); System.out.println("總計: " + 薪水); void 薪水() 薪水 = 工資;
JAVA程式設計 以[大富翁]為例
V8:抽象類別 -以玩家可以有不同個性為例 學習抽象類別 學習抽象方法 學習異質集合
//土地類別沒有更動 class 土地 { String 地名; int 過路費 = 20; int 地價; 玩家 地主; { this.地名 = 地名; } 土地(String name, int price) { 地名 = name; 地價 = price; void 登記土地所有權人(玩家 買地人) { if(買地人.錢 >= 地價) { //有錢可以買地 買地人.錢 -= 地價; 地主 = 買地人; System.out.println(買地人.名字 + "買了" + 地名); else System.out.println(買地人.名字 + "沒有買地"); return;
//土地2多加一個[登記土地所有權人]的方法,只是為了連結到父類別的[登記土地所有權人]方法 class 土地2 extends 土地 { int [] 過路費 = new int[6]; int 房子數 = 0; 土地2(String 地名) { super(地名); for(int i = 0; i < 過路費.length; i++) 過路費[i] = 20 + i*10; } 土地2(String name, int price) { super(name); 地價 = price; void 登記土地所有權人(玩家 買地人) { super.登記土地所有權人(買地人);
//將原來的類別改成抽象類別,並加上抽象方法[購地策略] abstract class 玩家 { String 名字; int 錢 = 2000; int 位置 = 0; 玩家(String name) { 名字 = name; } void 前進() { 位置 += ((int)(Math.random()*11) + 2); if (位置 >= 40) { 位置 -= 40; 錢 += 1000; return; void 付過路費(土地2 路過此地) { 錢 -= 路過此地.過路費[路過此地.房子數]; 路過此地.地主.錢 += 路過此地.過路費[路過此地.房子數]; System.out.println(名字 + "要付給 " + 路過此地.地名 + " 的地主 " +路過此地.地主.名字 + " " + 路過此地.過路費[路過此地.房子數] + " 元"); abstract void 購地策略(土地2 l);
//建立兩個子類別,並實作父類別的抽象方法[購地策略] class 我就是要買 extends 玩家 { 我就是要買(String name) { super(name); } void 購地策略(土地2 l) { l.登記土地所有權人(this); class 打死不買 extends 玩家 { 打死不買(String name) { System.out.println(名字 + "是打死不買,所以不買就是不買");
//在主程式的部份,利用異質陣列,只需在配置空間時,宣告為[打死不買]和[我就是要買]的不同類別物件 class 大富翁 { public static void main(String [] args) {String [] 土地名 = {"中華民國", "日本", "韓國", "菲律賓", "馬來西亞", "越南", "泰國", "印度", "伊拉克", "伊朗", "沙烏地阿拉伯", "土耳其", "以色列", "蘇俄", "波蘭", "德國", "奧地利", "瑞士", "法國", "比利時", "荷蘭", "英國", "芬蘭", "瑞典", "丹麥", "希臘", "義大利", "西班牙", "葡萄牙", "埃及", "摩洛哥", "南非", "加拿大", "美國", "墨西哥", "宏都拉斯", "尼加拉瓜", "古巴", "巴拿馬", "巴西"} ; String [] PN = {"喜羊羊", "美羊羊", "小灰灰", "灰太狼"}; 土地2 L [] = new 土地2[40]; for (int i = 0; i < L.length; i++) { if(i/2 * 2 == i) L[i] = new 土地2(土地名[i], i*20); else L[i] = new 土地2(土地名[i]); // L[i].地名 = 土地名[i]; System.out.println(L[i].地名 + " 地價是 " + L[i].地價); }
//異質陣列 玩家 P [] = new 玩家[4]; for (int i = 0; i < P //異質陣列 玩家 P [] = new 玩家[4]; for (int i = 0; i < P.length-2; i++) { P[i] = new 打死不買(PN[i]); } for(int i = 2; i < P.length; i++) { P[i] = new 我就是要買(PN[i]); //P[0].名字 = "喜羊羊"; //P[1].名字 = "美羊羊"; //P[2].名字 = "小灰灰"; //P[3].名字 = "灰太狼"; for(int j = 1; j <= 5; j++) { System.out.println("第 " + j + " 輪 "); /* System.out.println("目前土地狀態"); for (int k = 0; k < L.length; k++) if( L[k].地主 != null) System.out.println(L[k].地名 + " 地主是 " + L[k].地主.名字); else System.out.println(L[k].地名); */
for(int i = 0; i < P. length; i++) { P[i]. 前進(); System. out for(int i = 0; i < P.length; i++) { P[i].前進(); System.out.println(P[i].名字 + " 在 " + L[(P[i].位置)].地名 + ", 現在有錢" +P[i].錢); if(L[P[i].位置].地主 == null) { //買地 P[i].購地策略(L[P[i].位置]); // System.out.println(L[P[i].位置].地名 + "目前尚未售出"); // L[P[i].位置].登記土地所有權人(P[i]); } else { //付過路費 if (L[P[i].位置].地主 != P[i]) P[i].付過路費(L[P[i].位置]);