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

Java Java
スポンサーリンク

全体像

protected は「同じパッケージ内から見える」ことに加えて「別パッケージでも、継承したサブクラスからは自分自身を通して見える」アクセス修飾子です。外部に完全公開する public と、クラス内だけの private の中間に位置し、「継承前提の拡張ポイント」を安全に露出するために使います。フィールドよりもメソッド(フック)に付けるのが基本です。


見える範囲の正確なルール

同一パッケージでは通常どおり見える

protected メンバーは、同じパッケージ内なら他クラスから直接参照できます。これは package-private(修飾子なし)と同じ範囲です。

package a;
public class Base {
    protected String label = "base";
    protected String name() { return label; }
}
package a;
public class Peer {
    void demo() {
        var b = new Base();
        System.out.println(b.label); // OK(同一パッケージ)
        System.out.println(b.name()); // OK
    }
}
Java

異なるパッケージでは「サブクラスから自分自身に対してのみ」見える

別パッケージのサブクラスは、継承で得た protected メンバーを「自分自身(this)」に対して参照できます。ただし、親型の別インスタンスの protected には触れられません。

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(継承メンバーを自分自身で参照)
    }
    public void bad() {
        var other = new a.Base();
        // other.name();         // NG(別パッケージの他インスタンスの protected は不可)
        // System.out.println(other.label); // NG
    }
}
Java

適用対象と文脈ごとの注意点

クラス・メソッド・フィールド・コンストラクタへの適用

  • トップレベルのクラスには protected は付けられません(public か修飾子なしのみ)。
  • メソッドやフィールド、内部クラス、コンストラクタには protected を付けられます。
  • コンストラクタを protected にすると「同一パッケージとサブクラスからだけ生成可」にできます。
public class Shape {
    protected Shape() {} // サブクラスと同一パッケージからだけ new 可
}
public class Circle extends Shape { public Circle() { super(); } }
Java

インターフェースでは使えない

インターフェースのメンバーは暗黙に public(フィールドは public static final、メソッドは public)であり、protected は付けられません。継承の拡張ポイントは抽象クラス側で設計します。


何のために使うか(重要ポイントの深掘り)

継承の拡張ポイント(テンプレートメソッド)

protected は「サブクラスが差し替えるべき所」だけを露出するための道具です。外部 API(public)は薄く、内部の可変ポイントを protected で定義すると、拡張しやすく安全な設計になります。

public abstract class Report {
    public final String render() {                // 外部に見せる安定API
        return header() + body() + footer();
    }
    protected String header() { return "[HEADER]\n"; } // サブクラス差し替え可
    protected abstract String body();                  // 必須の拡張ポイント
    protected String footer() { return "\n[FOOTER]"; }
}
public final class SalesReport extends Report {
    @Override protected String body() { return "sales=123"; }
}
Java

内部状態は private、操作のフックは protected

フィールドを protected にすると、サブクラスから直接書き換えられて整合性が崩れやすくなります。原則はフィールド private、検証付きの操作(または読み取り専用のゲッター)でフックを提供します。

public abstract class Account {
    private int balance;                     // private で守る
    protected Account(int initial) { this.balance = initial; }
    protected final int balance() { return balance; } // 読み取りだけ露出
    protected final void add(int amount) { balance += amount; } // 検証付き操作を用意
}
public final class PointAccount extends Account {
    public PointAccount(int initial) { super(initial); }
    public void earn(int pts) { if (pts > 0) add(pts); } // ルールはサブクラス側で
}
Java

設計指針とアンチパターン

まずは private、必要なら protected(公開は最小限)

可視性は「最小公開」が原則。最初は private で閉じ、継承で必要な箇所だけを protected に広げます。むやみに protected にするとサブクラスが内部に依存し、変更が難しくなります。

継承より委譲を優先

protected に頼る設計は「継承」が前提ですが、拡張が複雑になるなら「インターフェース+委譲」で差し替えられる設計にする方が安全な場面は多いです。protected はどうしても継承にしたい拡張ポイントだけに絞ります。

別パッケージから「他インスタンスの protected」を触ろうとしない

ルール違反でコンパイルエラーになります。異なるパッケージでは、サブクラスから自分自身(this)分に限り参照可、という制約を忘れないでください。


例題で身につける

例 1: テンプレートメソッドで拡張可能な共通処理

public abstract class Importer {
    public final void run(String path) {
        var data = load(path);
        var normalized = normalize(data);
        save(normalized);
    }
    protected abstract String load(String path);     // 必須の拡張ポイント
    protected String normalize(String s) {           // 既定の実装。上書き可
        return s == null ? "" : s.trim().replaceAll("\\s+", " ");
    }
    protected abstract void save(String data);
}
public final class CsvImporter extends Importer {
    @Override protected String load(String path) { return "a,b,c"; }
    @Override protected void save(String data) { System.out.println("save:" + data); }
}
Java

例 2: protected コンストラクタで生成を制限

public class BaseRepo {
    protected BaseRepo(java.nio.file.Path root) { this.root = root; }
    private final java.nio.file.Path root;
    protected java.nio.file.Path root() { return root; }
}
public final class FileRepo extends BaseRepo {
    public FileRepo(java.nio.file.Path root) { super(root); }
}
Java

同一パッケージやサブクラスからのみ生成可能にし、設計意図を守ります。

例 3: 別パッケージのアクセス制約を体感

// package a
package a;
public class Base {
    protected int x = 1;
    protected int value() { return x; }
}
// package b
package b;
public class Sub extends a.Base {
    public int ok() { return this.value(); } // OK
    public int ng() {
        var other = new a.Base();
        // return other.value();             // NG
        return -1;
    }
}
Java

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

protected は「継承のための窓口」。同一パッケージでは通常どおり、別パッケージではサブクラスから“自分自身”に対してのみ見える。外部 API は public に限定し、内部状態は private、拡張のフックだけを protected に——これが安全で拡張しやすい筋の良い設計です。継承で迷うなら、まず委譲で解決できないかを検討し、それでも必要な最小限だけ protected を採用しましょう。

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