4-15:クラスとメンバーへのアクセス可能性を最小限にする
要点
クラスやメンバーの可視性(public/protected/package-private/private)は 必要最小限にする。公開するほど後方互換性の責任が増え、変更・誤用・継承バグのリスクが高まるため、まずは private にし、必要になったときだけ範囲を広げるのが安全。
なぜ「最小化」するのか
- API の面積が小さいほど保守しやすい: 公開したメンバーは将来変更しにくい(互換性を壊せない)。
- カプセル化が保てる: 実装詳細を隠すことで不整合や誤用が減る。
- テスト・リファクタが楽: 内部実装を自由に変えられる。
- セキュリティ/不変性の担保: 意図しない外部変更を防ぐ。
- 継承の危険を減らす: protected/public にするとサブクラスに依存され設計制約が増える。
悪い例
不要に public や protected を使っている
public class BankAccount {
public double balance; // NG: 直接書き換えられる
protected void recalcInterest() { ... } // NG: 継承者に内部処理をさらす
}
良い例
最小限に抑えた実装
public class BankAccount {
private double balance; // private にして不変性を守る
public double getBalance() { // 必要なら公開アクセサ
return balance;
}
// 内部処理は private にして、必要ならテスト用にパッケージプライベートにする
private void recalcInterest() { ... }
}
よくある具体パターンと指針
1. フィールドは基本的に private
- 直接 public フィールドを出すのは避ける(不変フィールドの public static final 定数は例外)。
- 外部アクセスが必要なら 読み取り専用メソッド(getter) を用意する。
2. メソッドは private → package-private → protected → public の順で考える
- テストのためにアクセスを広げるのは安易にやらない(代わりに単体テストは公開 API を通して行う、または package-private に留める)。
- protected は「サブクラス用だがパッケージ外でも公開される」ので乱用しない。継承を想定しないなら protected を使わない。
3. パッケージプライベート(デフォルト)を活用する
- 同一パッケージ内でのみ使う helper クラスやユーティリティは public にせず package-private にすることで API を小さく保てる。
4. ネストクラスは private static にする
- 実装の補助でしかない場合は private static class Helper { ... } にし、外部に見せない。
5. 定数は慎重に公開する
- public static final の定数は API の一部になる(変更が難しい)。内部だけで使う定数は private に。
6. 継承を許す/拡張ポイントは明示する
- クラスを継承可能にするなら、「どう拡張してよいか」「サブクラスが満たすべき不変条件」をドキュメント化する。
- 可能ならクラスは final にして、拡張を禁止する(安全)。
7. Java モジュール(Java 9+)でさらに隠す
- module-info.java を使えるならパッケージを exports しないことでモジュール外からの可視性を完全に遮断できる。
継承・API 設計に関する注意
- protected メソッドは実装の一部を公開することになり、将来の実装変更で後方互換性が壊れやすい。
- Effective Java の別項目(設計は拡張用に行うな)にもあるが、設計時に継承をサポートするのか否かを明確にする。サポートしないなら final にして protected を使わない。
まとめ
「アクセス可能性を最小化する」とは、まずは隠す(private)、必要になったら段階的に範囲を広げるという設計姿勢。これにより API の肥大化や将来の保守負担、不用意な継承や誤用を防げる。設計の堅牢性と変更しやすさを高める最もシンプルで効果の高いルール。