HOWTO · Java
在 Java 中擴展兩個類
了解 Java 類別為何只能擴展一個類別,以及介面與組合如何結合多種類型的行為。
本頁內容
Java 類別不能繼承兩個類別。每個類別只有一個直接父類別,因此像 class Child extends First, Second 這樣的宣告不是有效的 Java 語法。若要結合多種類型的行為,可以繼承一個類別並實作任意數量的介面,或持有其他物件,透過組合委派工作給它們。
為什麼 Java 類別不能繼承兩個類別
Java 類別採用單一繼承:每個類別在 extends 後最多只能指定一個父類別。這讓物件只有一條類別繼承鏈,也避免兩個父類別定義相同成員時,不清楚應採用哪個父類別的實作或狀態。編譯器會拒絕列出兩個父類別的類別宣告;這是編譯時的限制,不是程式執行期間才發生的錯誤。
以下是刻意寫成無效語法、無法編譯的 Java 程式碼:class Child extends First, Second {}。當新類別確實是某個類別的專門化類型時,請使用一個父類別。下一節將說明如何用介面契約描述額外的能力。
使用一個父類別並實作多個介面
類別可以繼承一個類別,並實作多個介面。父類別可以提供共用狀態或實作,而每個介面則宣告該類別承諾提供的能力。Java 關鍵字的用法很重要:類別使用 extends 指定父類別,並使用 implements 指定介面。介面本身可以繼承多個介面,但這不會讓類別擁有多個類別父類別。
以下完整範例定義兩個介面契約,並讓類別在繼承一個父類別的同時實作這兩個介面:
public class CatalogItem extends Record implements Printable, Identifiable {
CatalogItem(String name) {
super(name);
}
@Override
public void printLabel() {
System.out.println(name());
}
@Override
public String id() {
return "item-" + name().toLowerCase();
}
public static void main(String[] args) {
CatalogItem item = new CatalogItem("Book");
item.printLabel();
System.out.println(item.id());
}
}
interface Printable {
void printLabel();
}
interface Identifiable {
String id();
}
class Record {
private final String name;
Record(String name) {
this.name = name;
}
String name() {
return name;
}
}
Book
item-book
CatalogItem 繼承了 Record 的 name() 實作,並提供兩個介面所要求的方法。當彼此無關的類別需要共用一種類型或能力契約時,介面就很有用。介面不會像第二個父類別那樣,自動提供狀態或類別實作。
透過組合結合類別的行為
組合透過「擁有」關係來結合物件:類別儲存協作者的參照,並呼叫它們的方法。當行為來自各自可獨立使用的類別,或需要能替換協作者時,這種方式很實用。外層類別可以控制要公開哪些操作,只委派呼叫端需要的工作。
以下範例中的 SmartDevice 包含 PowerController 和 MessageSender,並將操作委派給它們:
public class SmartDevice {
private final PowerController power = new PowerController();
private final MessageSender sender = new MessageSender();
void start() {
power.powerOn();
}
void sendMessage(String message) {
sender.send(message);
}
public static void main(String[] args) {
SmartDevice device = new SmartDevice();
device.start();
device.sendMessage("Ready");
}
}
class PowerController {
void powerOn() {
System.out.println("Power on");
}
}
class MessageSender {
void send(String message) {
System.out.println("Sending: " + message);
}
}
Power on
Sending: Ready
SmartDevice 並不會變成 PowerController 或 MessageSender;它只是使用這兩個物件。在較大型的程式中,可以透過建構子傳入協作者,或以介面表示協作者,這樣更容易替換實作,也能獨立測試外層類別。
選擇合適的重用模式
當新類型確實是某個父類別的專門化形式,而且需要繼承其實作時,請選擇類別繼承。當類型需要承諾具備其他類別也共用的能力時,請加入介面。當行為需要由多個部分組成、委派出去,或能獨立變更時,請使用組合。
讓每個類別專注於明確的職責,只公開呼叫端需要的操作。介面契約宜小而精,避免過於寬泛;也不要重複實作原本適合放在共用協作者中的行為。這些做法能讓類別之間的關係更容易理解、測試,並隨需求變化調整。簡言之,Java 不允許類別繼承兩個類別;一個父類別、多個介面契約,以及透過組合使用協作者,足以應付常見的重用需求。