Java 抽象類別 vs 介面

抽象類別和介面都是實現抽象化的方式,但它們有不同的用途和限制。

快速比較

特性抽象類別介面
關鍵字abstract classinterface
繼承/實作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 方法
擴展性需要修改類別階層新增介面即可
  • 優先使用介面,因為更靈活
  • 當需要共享狀態或程式碼時使用抽象類別
  • 可以同時使用:介面定義契約,抽象類別提供部分實作