Java | オブジェクト指向:private の意味

Java Java
スポンサーリンク

全体像

private は「同じクラスの中からだけ見える」というアクセス修飾子です。外部のクラスはもちろん、継承したサブクラスからも直接は触れません。目的はカプセル化——内部の状態や実装詳細を隠し、外部には安全な操作だけを“窓口”として見せること。これにより、整合性が守られ、変更に強く、誤用されない設計になります。


使える対象と基本ルール

private はフィールド(インスタンス変数/static 変数)、メソッド、コンストラクタ、内部クラス(ネストクラス)に付けられます。トップレベルのクラスには付けられません。private メンバーは「そのクラスの本文内」からしか参照できず、他クラスはゲッターや適切な操作メソッド経由でしか関与できません。

public final class Account {
    private int balance;                 // 外部から直接触れない
    private static final int FEE = 100;  // 手数料の実装詳細(外へ隠す)

    public Account(int initial) {
        if (initial < 0) throw new IllegalArgumentException("initial>=0");
        this.balance = initial;
    }

    public int balance() {               // 読み取りの窓口(契約)
        return balance;
    }

    public boolean withdraw(int amount) { // 変更の窓口(検証付き)
        if (amount <= 0 || amount + FEE > balance) return false;
        balance -= (amount + FEE);
        return true;
    }

    private void audit(String msg) {     // 内部だけの補助
        System.out.println("[AUDIT] " + msg);
    }
}
Java

カプセル化の目的(重要ポイントの深掘り)

private の最大の価値は「整合性ルールを破られない」ことです。フィールドを public にすると、どこからでも値を自由に書き換えられ、バリデーションをすり抜けます。private にして検証付きメソッドを窓口化すれば、常に正しい状態だけが許されます。さらに、内部実装を外から隠すことで、後から中身を差し替えても外部のコードは壊れません。これは変更耐性(後方互換性)を高める鍵です。

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 にする設計

コンストラクタを private にすると、外部からの new を禁止できます。代わりに static ファクトリ(of や create)を用意すると、生成ルールを一箇所へ集約でき、キャッシュやバリデーションも管理しやすくなります。複雑な前処理・共通化したいロジックは private ヘルパーメソッドに閉じ込めると、公開APIを“薄く・安定”に保てます。

public final class Email {
    private final String value;
    private Email(String value) { this.value = value; }       // 生成の入口を閉じる

    public static Email of(String raw) {                      // 公開ファクトリ
        var v = normalize(raw);
        if (!isValid(v)) throw new IllegalArgumentException("invalid email");
        return new Email(v);
    }

    public String value() { return value; }

    private static String normalize(String s) {               // 内部ヘルパー
        return s == null ? "" : s.trim().toLowerCase(java.util.Locale.ROOT);
    }
    private static boolean isValid(String s) {
        int at = s.indexOf('@');
        return at > 0 && at == s.lastIndexOf('@') && at < s.length() - 1;
    }
}
Java

内部クラスと private の使い道

内部の一時的な構造やパーサ用のトークンなど、外へ見せる必要がない型は private の内部クラスに閉じると安全です。外側クラスだけが利用でき、APIの表面積を増やさずに済みます。テストで内部を直接触りたくなるときは、公開メソッド経由で振る舞いを検証するか、パッケージ構成を見直して内部を package-private にする方が健全です。

public final class Parser {
    public Result parse(String s) {
        var tks = tokenize(s);
        return buildResult(tks);
    }

    private static class Token {          // 外へは見せない内部型
        final String text;
        Token(String text) { this.text = text; }
    }

    private java.util.List<Token> tokenize(String s) {
        var list = new java.util.ArrayList<Token>();
        for (var part : (s == null ? "" : s.trim()).split("\\s+")) {
            if (!part.isEmpty()) list.add(new Token(part));
        }
        return list;
    }

    private Result buildResult(java.util.List<Token> tokens) {
        return new Result(tokens.size());
    }
}

public record Result(int count) {}
Java

よくある誤解と回避策(重要ポイントの深掘り)

「テストで触りたいから public にする」は誤りです。可視性を緩めると本番コードも内部詳細に依存し、変更が難しくなります。テストは公開APIを通じて振る舞いを検証し、必要ならパッケージ境界を工夫して package-private を活用しましょう。また、getter/setter を機械的に public で生成するより、「意味のある操作」を公開する方が整合性と可読性が高まります。さらに、private フィールド+final による不変設計は並行性とテストの強力な味方です。

public final class Token {
    private final String value;          // 不変+private
    public Token(String v) {
        if (v == null || v.isBlank()) throw new IllegalArgumentException("token");
        this.value = v;
    }
    public String value() { return value; }
}
Java

例題で身につける

// 1) 不正な初期状態を拒否し、内部詳細は隠す
public final class Config {
    private String region = "ap-northeast-1";   // 既定値は内部管理
    private int timeoutMs = 3000;

    public Config(String region, int timeoutMs) {
        if (timeoutMs <= 0) throw new IllegalArgumentException("timeout>0");
        if (region != null && !region.isBlank()) this.region = region.trim();
        this.timeoutMs = timeoutMs;
    }
    public String region() { return region; }   // 読み取りのみ公開
    public int timeoutMs() { return timeoutMs; }
}

// 2) 生成と検証の集中(private コンストラクタ+ファクトリ)
public final class Url {
    private final java.net.URI uri;
    private Url(java.net.URI uri) { this.uri = uri; }        // 外から new させない
    public static Url parse(String s) {
        try { return new Url(new java.net.URI(s)); }
        catch (java.net.URISyntaxException e) { throw new IllegalArgumentException("bad url: " + s, e); }
    }
    public java.net.URI toUri() { return uri; }
}
Java

仕上げのアドバイス(重要部分のまとめ)

private は「クラスの外から触らせない」ための鍵で、カプセル化の中心です。フィールドや内部ヘルパーは private に閉じ、外へ見せるのは検証付きの“意味のある操作”だけに絞る。必要ならコンストラクタを private にしてファクトリへ集約し、内部型は private の内部クラスに閉じる。テストを理由に可視性を緩めず、公開APIの契約で振る舞いを検証する——この基本を徹底すれば、整合性が守られ、変更に強く、読みやすいクラス設計が手に入ります。

タイトルとURLをコピーしました