Java | オブジェクト指向:アクセス修飾子の種類

Java Java
スポンサーリンク

全体像

アクセス修飾子は「どこからそのクラス・フィールド・メソッドが見えるか(使えるか)」を制御するための言語機能です。公開範囲を絞ることで、意図しない使われ方や整合性の破壊を防ぎ、APIの進化に強くできます。Java の基本は public、protected、package-private(修飾子なし)、private の4種類。トップレベルのクラスに使えるのは public と package-private のみです。


種類と見える範囲の違い

public(どこからでも見える)

クラス・メンバーがどのパッケージからも参照可能。ライブラリの公開APIやエントリポイントに使います。

// 別パッケージからも利用可能
public class User {
    public String name() { return "Taro"; }
}
Java
  • 使いどころ: 外部に提供する契約(API)
  • 注意点: 一度公開すると後方互換性の制約が重くなるため、むやみに増やさない

package-private(修飾子なし:同一パッケージ内だけ)

修飾子を付けない場合の既定。パッケージ内部の協調のために使い、外部からは隠します。

// top-level クラスに多用。内部モジュール用
class InternalService {
    String run() { return "ok"; }
}
Java
  • 使いどころ: 実装詳細、ヘルパー、テスト支援のダブル
  • 注意点: パッケージの境界設計が重要(粒度を適切に)

protected(同パッケージ+サブクラスから)

同一パッケージ内では通常に参照可能。異なるパッケージの場合は「サブクラスから自分自身(this)に対して」参照可能です。

package a;
public class Base {
    protected String label = "base";
    protected String name() { return label; }
}
package b;
public class Sub extends a.Base {
    public String show() {
        return this.name();     // OK(継承で this に対して)
        // new a.Base().name(); // NG(他インスタンスの protected には触れない)
    }
}
Java
  • 使いどころ: 継承前提の拡張ポイント(テンプレートメソッドなど)
  • 注意点: 外部からの乱用を防ぐため、protected にする前に「委譲+public 最小」や「package-private で同一パッケージ運用」を検討

private(同一クラス内だけ)

クラスの内部実装を完全に隠します。カプセル化の要。

public final class Account {
    private int balance;               // 直接触らせない
    public int balance() { return balance; }
    public void deposit(int amount) {
        if (amount <= 0) throw new IllegalArgumentException();
        balance += amount;
    }
}
Java
  • 使いどころ: フィールド、実装詳細、ヘルパーメソッド
  • 注意点: テストのために可視性を緩めない(パッケージ境界や公開メソッドで検証する)

トップレベルとネストの違い(クラス定義の文脈)

トップレベルのクラスに使える修飾子

トップレベル(ファイル直下)のクラスは public か package-private(修飾子なし)のみ。private・protected は不可です。

// 公開API
public class Api { /* ... */ }

// 内部用(修飾子なし)
class Impl { /* ... */ }
Java

ネスト(内部)クラスの可視性

内部クラス(static/非static)のメンバーは、private も含めて柔軟に制御できます。外からは隠し、外側クラスだけに見せる設計が可能です。

public class Parser {
    private static class Token { /* Parser 内専用 */ }
    public Result parse(String s) { /* Token を内部で使用 */ return new Result(); }
}
Java

設計の指針(重要ポイントの深掘り)

まずは最小公開(private → package-private → protected → public)

  • 原則: 初期は private で閉じ、必要になった範囲にだけ段階的に広げる
  • 効果: 依存が減り、変更が容易になる。外部の破壊的利用を防げる

フィールドは原則 private、操作は検証付きメソッドで

  • 狙い: 整合性を守る(直接代入の禁止)
  • 例: ゲッター・セッターではなく「意味のある操作」を公開
public final class Product {
    private int price;
    public void reprice(int newPrice) { if (newPrice >= 0) this.price = newPrice; }
}
Java

protected は継承のための契約。乱用しない

  • 狙い: 拡張ポイントだけを意図的に露出
  • 代替: インターフェース+委譲(テスト容易・差し替え自由)

パッケージ設計が可視性設計

  • 狙い: package-private を活用し、内部実装はパッケージに閉じる
  • 実務: domain/infrastructure/app などレイヤごとにパッケージを分けると境界が明確

例題で体験する可視性の差

例 1: ライブラリの公開APIと内部実装の分離

// com.example.api
package com.example.api;
public final class Email {
    private final String value;
    public Email(String raw) {
        var v = raw == null ? "" : raw.trim().toLowerCase();
        if (!v.contains("@")) throw new IllegalArgumentException();
        this.value = v;
    }
    public String value() { return value; }
}

// com.example.internal
package com.example.internal;
class EmailNormalizer { // 外から見えない
    static String normalize(String s) { return s == null ? "" : s.trim().toLowerCase(); }
}
Java

APIは public、内部ヘルパーは package-private にして外から隠します。

例 2: 継承で protected を使う拡張ポイント

package app;
public abstract class Shape {
    protected abstract double area(); // サブクラスが実装
    public String describe() { return "area=" + area(); } // 共通の公開API
}
public final class Circle extends Shape {
    private final double r;
    public Circle(double r) { this.r = r; }
    @Override protected double area() { return Math.PI * r * r; }
}
Java

外へ見せるのは public describe()、実装の差分は protected area() に限定。

例 3: フィールドを private にして操作で公開

public final class Cart {
    private final java.util.List<Item> items = new java.util.ArrayList<>();
    public void add(Item it) {
        if (it == null || it.price() < 0) throw new IllegalArgumentException();
        items.add(it);
    }
    public int total() { return items.stream().mapToInt(Item::price).sum(); }
}
public record Item(String name, int price) {}
Java

直接 items を公開せず、意味のある操作だけ公開します。


よくあるつまずきと回避

なんでも public にしてしまう

見えるほど依存が増え、変更が困難に。最小公開を徹底し、外に見せるのは「契約」だけに絞る。

protected の誤解(他インスタンスに触れる)

異なるパッケージでは、サブクラスから「自分の継承メンバー」だけ触れる。親型の別インスタンスの protected には触れない。

フィールドを public にする

整合性が壊れる。必ず private で閉じ、検証付きメソッドで操作。

テストのために可視性を緩める

テストは公開API経由で振る舞いを検証。どうしても内部を見るならパッケージ構成を見直すか、テスト用ファクトリを検討。


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

アクセス修飾子は「依存の境界」を作る道具。クラスの公開は最小に、フィールドは private、実装詳細は package-private に閉じ、継承ポイントだけを protected で露出。public は契約に限る。これだけで、読みやすさ・安全性・変更耐性が跳ね上がります。

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