Java 抽象類別 vs 介面
抽象類別和介面都是實現抽象化的方式,但它們有不同的用途和限制。
快速比較
| 特性 | 抽象類別 | 介面 |
|---|---|---|
| 關鍵字 | abstract class | interface |
| 繼承/實作 | extends(單一繼承) | implements(多重實作) |
| 建構子 | 可以有 | 不可以 |
| 成員變數 | 任意 | 只能 public static final |
| 方法 | 抽象和具體都可以 | 抽象、default、static |
| 存取修飾子 | 任意 | 預設 public |
| 多重繼承 | 不支援 | 支援 |
抽象類別
abstract class Animal {
// 成員變數
protected String name;
private int age;
// 建構子
public Animal(String name) {
this.name = name;
}
// 具體方法
public void sleep() {
System.out.println(name + " 正在睡覺");
}
// 抽象方法
public abstract void speak();
public abstract void move();
}
class Dog extends Animal {
public Dog(String name) {
super(name); // 呼叫父類別建構子
}
@Override
public void speak() {
System.out.println(name + " 汪汪叫");
}
@Override
public void move() {
System.out.println(name + " 四腳走路");
}
}
介面
interface Flyable {
// 常數(自動是 public static final)
int MAX_ALTITUDE = 10000;
// 抽象方法(自動是 public abstract)
void fly();
// default 方法(Java 8+)
default void land() {
System.out.println("降落中...");
}
// static 方法(Java 8+)
static void checkWeather() {
System.out.println("檢查天氣");
}
}
interface Swimmable {
void swim();
}
// 實作多個介面
class Duck implements Flyable, Swimmable {
@Override
public void fly() {
System.out.println("鴨子飛行");
}
@Override
public void swim() {
System.out.println("鴨子游泳");
}
}
何時使用抽象類別
1. 需要共享程式碼
當子類別需要共享相同的實作時:
abstract class Employee {
protected String name;
protected double baseSalary;
public Employee(String name, double baseSalary) {
this.name = name;
this.baseSalary = baseSalary;
}
// 共享的方法
public String getName() {
return name;
}
// 共享的計算邏輯
public double getBaseSalary() {
return baseSalary;
}
// 子類別必須實作
public abstract double calculateBonus();
public double getTotalSalary() {
return baseSalary + calculateBonus();
}
}
class Manager extends Employee {
private int teamSize;
public Manager(String name, double baseSalary, int teamSize) {
super(name, baseSalary);
this.teamSize = teamSize;
}
@Override
public double calculateBonus() {
return teamSize * 1000; // 每個團隊成員加 1000
}
}
2. 需要非 public 成員
abstract class DatabaseConnection {
private String connectionString; // private 變數
protected Connection connection; // protected 變數
protected void log(String message) { // protected 方法
System.out.println("[DB] " + message);
}
}
3. 需要建構子
abstract class Shape {
protected String color;
public Shape(String color) {
this.color = color;
}
}
何時使用介面
1. 定義能力/行為
interface Comparable<T> {
int compareTo(T o);
}
interface Serializable {
// 標記介面
}
interface Runnable {
void run();
}
2. 需要多重繼承
interface Printable {
void print();
}
interface Scannable {
void scan();
}
interface Faxable {
void fax();
}
// 一個類別可以實作多個介面
class AllInOnePrinter implements Printable, Scannable, Faxable {
@Override
public void print() { /* ... */ }
@Override
public void scan() { /* ... */ }
@Override
public void fax() { /* ... */ }
}
3. 定義契約
interface PaymentProcessor {
boolean processPayment(double amount);
boolean refund(String transactionId);
}
class CreditCardProcessor implements PaymentProcessor {
@Override
public boolean processPayment(double amount) { /* ... */ }
@Override
public boolean refund(String transactionId) { /* ... */ }
}
class PayPalProcessor implements PaymentProcessor {
@Override
public boolean processPayment(double amount) { /* ... */ }
@Override
public boolean refund(String transactionId) { /* ... */ }
}
組合使用
最佳實踐是同時使用抽象類別和介面:
// 介面定義能力
interface Drawable {
void draw();
}
interface Resizable {
void resize(double factor);
}
// 抽象類別提供共享實作
abstract class Shape implements Drawable {
protected String color;
protected double x, y;
public Shape(String color, double x, double y) {
this.color = color;
this.x = x;
this.y = y;
}
public void moveTo(double x, double y) {
this.x = x;
this.y = y;
}
public abstract double area();
}
// 具體類別
class Circle extends Shape implements Resizable {
private double radius;
public Circle(String color, double x, double y, double radius) {
super(color, x, y);
this.radius = radius;
}
@Override
public void draw() {
System.out.println("畫圓形");
}
@Override
public double area() {
return Math.PI * radius * radius;
}
@Override
public void resize(double factor) {
radius *= factor;
}
}
Java 8+ 介面新功能
default 方法
interface Vehicle {
void start();
default void horn() {
System.out.println("按喇叭!");
}
}
class Car implements Vehicle {
@Override
public void start() {
System.out.println("引擎啟動");
}
// horn() 使用預設實作
}
解決 default 方法衝突
interface A {
default void method() {
System.out.println("A");
}
}
interface B {
default void method() {
System.out.println("B");
}
}
class C implements A, B {
@Override
public void method() {
A.super.method(); // 選擇 A 的實作
// 或 B.super.method();
// 或完全自訂
}
}
static 方法
interface StringUtils {
static boolean isEmpty(String s) {
return s == null || s.isEmpty();
}
static String reverse(String s) {
return new StringBuilder(s).reverse().toString();
}
}
// 使用
boolean empty = StringUtils.isEmpty("");
String reversed = StringUtils.reverse("hello");
private 方法(Java 9+)
interface Logger {
default void logInfo(String msg) {
log("INFO", msg);
}
default void logError(String msg) {
log("ERROR", msg);
}
private void log(String level, String msg) {
System.out.println("[" + level + "] " + msg);
}
}
設計準則
使用抽象類別當...
- 子類別之間有明確的「是一種」(is-a) 關係
- 需要共享程式碼和狀態
- 需要定義非 public 成員
- 需要建構子來初始化狀態
使用介面當...
- 定義行為契約
- 需要多重繼承
- 不相關的類別需要相同的行為
- 想要解耦合
經驗法則
┌─────────────────────────┐
│ 需要多重繼承? │
└───────────┬─────────────┘
│
┌───────────┴───────────┐
Yes No
│ │
▼ ▼
┌─────────┐ ┌─────────────────────┐
│ 介面 │ │ 需要共享狀態/程式碼?│
└─────────┘ └──────────┬──────────┘
│
┌───────────┴───────────┐
Yes No
│ │
▼ ▼
┌───────────┐ ┌─────────┐
│ 抽象類別 │ │ 介面 │
└───────────┘ └─────────┘
重點整理
| 選擇 | 抽象類別 | 介面 |
|---|---|---|
| 關係 | is-a(是一種) | can-do(能做) |
| 繼承 | 單一 | 多重 |
| 狀態 | 可以有 | 只能有常數 |
| 建構子 | 可以有 | 不能有 |
| 程式碼複用 | 繼承 | default 方法 |
| 擴展性 | 需要修改類別階層 | 新增介面即可 |
- 優先使用介面,因為更靈活
- 當需要共享狀態或程式碼時使用抽象類別
- 可以同時使用:介面定義契約,抽象類別提供部分實作