全体像
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 を採用しましょう。
