まとめて言うと何か
カプセル化は「内部の状態や仕組みを隠し、外からは安全な窓口だけを見せる」設計のことです。目的は3つに絞られます。整合性の保護(壊れない)、変更容易性(中身を差し替えても外が壊れない)、理解容易性(使い方が明確)。結果として、バグが減り、拡張とテストが楽になります。
何が問題になるのか(カプセル化がない世界)
直接代入で整合性が壊れる
外部がフィールドへ直接触れると、禁止したい値が簡単に入り、オブジェクトの前提が崩れます。
// カプセル化が弱い例
public class Product {
public int price; // どこからでも書き換え可能
}
var p = new Product();
p.price = -100; // 本来あり得ない状態になる
Java実装詳細への依存で変更できなくなる
内側の構造(コレクションの種類、丸め規則など)を外に晒すと、それに依存したコードが増え、差し替えが困難になります。
カプセル化が何を守るか(重要ポイントの深掘り)
不変条件(インバリアント)を入り口で保証する
「価格は0以上」「IDは空でない」などのルールを、コンストラクタや操作メソッドの“入り口”で必ず検証します。これが壊れないオブジェクトの基本です。
public final class Product {
private int price; // 外から直接触れさせない
public Product(int price) {
if (price < 0) throw new IllegalArgumentException("price>=0");
this.price = price;
}
public int price() { return price; } // 読み取りの窓口
public void reprice(int newPrice) { // 変更の窓口(検証付き)
if (newPrice < 0) throw new IllegalArgumentException("price>=0");
this.price = newPrice;
}
}
Java「意味のある操作」を公開することで、状態は常に正しく保たれます。
実装詳細を隠して、外は“契約”だけにする
内部の構造・アルゴリズム・補助メソッドは非公開(private/package-private)。外には、明確な契約(引数の前提、失敗時の例外、戻り値の意味)で薄いAPIだけを見せます。
public final class Email {
private final String value;
public Email(String raw) {
var v = normalize(raw); // 内部前処理に集約
if (!isValid(v)) throw new IllegalArgumentException("invalid email");
this.value = v;
}
public String value() { return value; }
// 実装詳細は隠す
private static String normalize(String s) { return s == null ? "" : s.trim().toLowerCase(); }
private static boolean isValid(String s) {
int at = s.indexOf('@');
return at > 0 && at == s.lastIndexOf('@') && at < s.length() - 1;
}
}
Java変更容易性(内部差し替えの自由)
内部の型や処理を変えても、公開APIを保てば外部は影響を受けません。これは将来の最適化・仕様変更にとって極めて重要です。
不変設計と副作用の扱い
不変に寄せるとさらに安全になる
final フィールドだけを持つ不変オブジェクトは、作成後に状態が変わりません。並行性・テストが強く、共有しても安全です。
public final class Money {
private final int amount;
private final String currency;
public Money(int amount, String currency) {
if (amount < 0) throw new IllegalArgumentException("amount>=0");
if (currency == null || currency.isBlank()) throw new IllegalArgumentException("currency");
this.amount = amount;
this.currency = currency;
}
public Money addTax(double rate) { // 新インスタンスを返す(副作用なし)
int taxed = (int) Math.round(amount * (1 + rate));
return new Money(taxed, currency);
}
public int amount() { return amount; }
public String currency() { return currency; }
}
Java副作用は窓口で明確に、必ず検証付きに
状態を変えるメソッドは少数に絞り、必ず検証・例外方針を明確にします。副作用の場所が限定されるほど、追跡と保守が楽になります。
APIの表面積を最小にする
ゲッター・セッターより「意味のある操作」
値の読み取りは最小限に、書き換えはドメイン動詞で表現し、ルールを埋め込みます。セッター乱用は整合性の抜け道になりがちです。
public final class BankAccount {
private int balance;
public BankAccount(int initial) {
if (initial < 0) throw new IllegalArgumentException();
this.balance = initial;
}
public int balance() { return balance; }
public boolean withdraw(int amount) {
if (amount <= 0 || amount > balance) return false;
balance -= amount;
return true;
}
public void deposit(int amount) {
if (amount <= 0) throw new IllegalArgumentException();
balance += amount;
}
}
Java例題で身につける(カプセル化の適用)
例 1: 内部ヘルパーの隠蔽と薄い公開API
public final class Url {
private final java.net.URI uri;
public Url(String s) {
try { this.uri = new java.net.URI(s); }
catch (java.net.URISyntaxException e) { throw new IllegalArgumentException("bad url: " + s, e); }
}
public boolean isHttp() { return "http".equalsIgnoreCase(uri.getScheme()); } // 契約はここだけ
}
Java外は「安全に作る」「問い合わせる」だけの窓口。内部の解析ロジックを変えても外は不変。
例 2: 料金計算の統一(丸め規則を内部に固定)
public final class PriceCalculator {
public int withTax(int base, double rate) {
if (base < 0 || rate < 0) throw new IllegalArgumentException();
return (int) Math.round(base * (1 + rate)); // 丸め規則をここで固定
}
}
Java利用者が勝手に違う丸めをしないよう、窓口で統一する。
よくある落とし穴と回避
フィールドを public にしてしまう
整合性が壊れる最短ルート。必ず private にし、検証付きメソッドで操作する。
セッター乱用
全フィールドに自由なセッターを付けると、オブジェクトの前提が簡単に破壊されます。動詞ベースの操作に置き換え、ルールを窓口に埋め込む。
テストのために可視性を緩める
公開API経由で振る舞いを検証するのが基本。内部に触りたいなら、同一パッケージにテストを置き package-private を活用するか、設計を見直す。
仕上げのアドバイス(重要部分のまとめ)
カプセル化の目的は「壊れない・変えやすい・分かりやすい」を同時に満たすこと。フィールドは private、不変条件はコンストラクタと操作の入り口で保証、内部詳細は隠して薄い契約だけを public に。副作用は少数の窓口に限定し、不変に寄せる——この型を守るだけで、設計は見通し良く、変更にも強くなります。
