整理 Effective Java 書中 Item 16: In public classes, use accessor methods, not public fields 心得筆記

主旨

公開類別若直接暴露成員欄位(如 public int x),將會失去封裝帶來的所有好處:無法控制欄位存取、難以變更內部結構、也無法強制不變條件。正確做法是:使用私有欄位,並搭配 public 的 getter / setter 方法進行存取。

問題在哪?直接公開欄位的壞處

// 錯誤示範:沒有封裝的類別 Point
public class Point {
    public double x;
    public double y;
}

上面的設計問題如下:

  • 無法在存取欄位時執行額外邏輯(例如驗證、事件觸發)
  • 欄位型別或結構若改變(如 double 改成 BigDecimal),會破壞所有使用該欄位的程式碼
  • 外部程式可以任意修改欄位內容,導致不可預期的行為

換句話說,欄位的公開等於放棄了封裝

正確作法:使用 accessor(getter/setter)

public class Point {
    private double x;
    private double y;

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double getX() { return x; }
    public double getY() { return y; }

    public void setX(double x) { this.x = x; }
    public void setY(double y) { this.y = y; }
}

好處包括:

  • 可以在 setter 加上驗證或額外邏輯
  • 保持欄位可控性,減少 bug 機率
  • 未來可替換欄位類型或內部儲存方式,只需調整實作,不會影響使用端

例外情況:封裝不必要那麼硬派?

如果類別是:

  • package-private(同一個 package 使用)
  • private 的 nested class(僅限內部使用)

那麼,適度直接公開欄位是可以接受的。原因是這些類別本來就不會被外部依賴,不會造成「變動影響廣泛」的問題。

這種做法可以減少程式碼冗長,提高可讀性與開發效率。例如:

class Rectangle {
    static class Point {
        double x, y; // OK,因為是 private nested class
    }
}

但如果你哪天把這個類別改成 public,就必須馬上改為封裝設計。

公開不可變欄位可以嗎?

偶爾會見到這樣的設計:

public final class Time {
    public final int hour;
    public final int minute;

    public Time(int hour, int minute) {
        if (hour < 0 || hour >= 24) throw new IllegalArgumentException();
        if (minute < 0 || minute >= 60) throw new IllegalArgumentException();
        this.hour = hour;
        this.minute = minute;
    }
}

這樣做的問題不在於欄位會被改動(因為是 final),而是未來你無法輕易變更內部設計,例如將 hour 換成 LocalTime。只要欄位公開了,你就失去了調整的彈性。

所以,即使欄位是不可變的,也應該預設採取封裝設計。

小結

  • public 類別絕對不要暴露可變欄位
  • 即便是不可變欄位,也建議改用 getter
  • 想要簡潔可控的程式碼,請善用 private + accessor
  • 封裝不是形式,是為了 靈活、可控、可維護