Effective Java 4-17:可変性を最小限をする
要点
可能なかぎり不変(immutable)にし、どうしても変更が必要な箇所は局所化して可変性の範囲(スコープ・API)を最小化する。
なぜ可変性を最小化するのか
- バグが減る: 状態変化が少ないほど思考負荷が下がり、誤った状態遷移によるバグが減る。
- スレッド安全が簡単: 不変オブジェクトはスレッド間で安全に共有できる(ロック不要)。
- 設計が安定する: 不変なら内部表現を自由に変えられる(カプセル化が強化される)。
- テストしやすい: 入力→出力が一定なので単体テストが単純化される。
不変クラスを作るための「ルール」(必須チェックリスト)
- クラスを final にする(サブクラス化を禁止)。
- すべてのフィールドを private final にする。
- this の参照を公開しない(this を公開フィールド・コールバックで渡さない)。
- 可変オブジェクトをフィールドに持つときは防御的コピーを行う(コンストラクタでコピー、getter でコピー or 不変ビューを返す)。
- メソッドは状態を変更するものを持たない(setter を持たない)。
- equals/hashCode/toString を適切に実装する(不変ならハッシュキャッシュが可能)。
- シリアライズ可能にするなら readResolve 等で不変性を保つ配慮をする。
悪い例
public class BadPerson {
public String name; // public で直接書き換え可能
public int[] tags; // 配列参照をそのまま保持 -> 共有される
}
良い例
public final class Person {
private final String name;
private final int[] tags; // 内部は不変に見せるために defensive copy
public Person(String name, int[] tags) {
this.name = Objects.requireNonNull(name);
this.tags = (tags == null) ? null : tags.clone(); // 防御的コピー
}
public String getName() { return name; }
public int[] getTags() {
return (tags == null) ? null : tags.clone(); // コピーを返す -> 内部は守られる
}
}
配列/コレクションを扱うときの注意
- フィールドに List や Map を持つなら、コンストラクタでコピーし(new ArrayList<>(incoming))、公開時は不変ビューを返す(Collections.unmodifiableList(...))。
- Collections.unmodifiableList は内部参照が変更されたら見た目も変わる点に注意(完全な防御にはコピー + unmodifiable を組合せる)。
this.items = Collections.unmodifiableList(new ArrayList<>(items));
public List<Item> getItems() { return items; } // 安全
フィールド数が多い/複雑な構築は Builder を使う
不変かつ多フィールドのクラスは Builder パターン を使うと可読性と安全性が両立します。
public final class Complex {
private final String a;
private final int b;
private final List<String> tags;
private Complex(Builder b) {
this.a = b.a;
this.b = b.b;
this.tags = Collections.unmodifiableList(new ArrayList<>(b.tags));
}
public static class Builder {
private String a;
private int b;
private List<String> tags = new ArrayList<>();
public Builder a(String a){ this.a = a; return this; }
public Builder b(int b){ this.b = b; return this; }
public Builder addTag(String t){ tags.add(t); return this; }
public Complex build(){ return new Complex(this); }
}
}
ハッシュコードのキャッシュ(不変クラスの利点)
不変なら hashCode() を一度計算してキャッシュできる(高頻度でハッシュされる場合に有効):
private final int cachedHash;
public MyImmutable(...) {
// assign fields
cachedHash = computeHash();
}
@Override public int hashCode() { return cachedHash; }
まとめ
- まず不変にできないか考える。ほとんどのクラスは不変にできるか、少なくとも可変性を局所化できる。
- 不変にすればバグが減り、スレッド安全で、将来の改変が容易になる。
- 可変であるならば、その範囲と公開 API を最小化して、外部に mutable な参照を漏らさないこと。