Java道|継承におけるメンバアクセスのしくみ

親子の関係でも、すべてを直接見られるわけではない。
private と protected を理解すると、継承の中で守る情報と共有する情報の境界線がはっきり見えてきます。

Javaの継承では、スーパークラスの性質や機能をサブクラスが受け継げます。
しかし、ここで大切なのは、受け継いだからといって、すべてのメンバに自由に直接アクセスできるわけではない という点です。

クラスのフィールドやメソッドには、どこからアクセスできるかを決める アクセス修飾子 を付けられます。
今回の中心になるのは、private と protected です。

鬼滅の刃風にたとえると、DemonSlayer クラスは鬼殺隊士としての共通情報を持つ親クラスです。
そこから PillarSlayer クラスという柱の隊士クラスを作るとします。

柱は鬼殺隊士の一種なので、鬼殺隊士としての情報を受け継ぎます。
けれども、親クラスが内部だけで守りたい情報まで、子クラスが勝手に直接触れるわけではありません。

たとえば、隊士名簿の中には次のような情報があります。

情報の種類鬼滅の刃風のイメージアクセス修飾子
親クラスだけで厳重に管理したい情報隊士名簿の奥にある内部管理情報private
子クラスにも使わせたい情報柱にも共有される隊士の基本情報protected

つまり、継承では「親から子へ受け継ぐ」ことと、「子から直接触れる」ことを分けて考える必要があります。
この境界線を作るのが、private と protected です。

継承したら親クラスのメンバは何でも使えるのか

まず結論からいうと、継承したからといって、スーパークラスのメンバすべてにサブクラスから直接アクセスできるわけではありません。

ここはとても大事です。

サブクラスはスーパークラスの性質を受け継ぎます。
しかし、実際にそのメンバへ直接アクセスできるかどうかは、アクセス修飾子によって決まります。

たとえば、親クラスにあるフィールドが private なら、そのフィールドは親クラスの内部だけで使える情報になります。
サブクラスであっても、その private フィールドを直接読むことはできません。

鬼滅の刃風にたとえると、PillarSlayer は DemonSlayer を受け継いだ存在です。
しかし、DemonSlayer の内部保管庫にある情報を、PillarSlayer が勝手に開けることはできません。

親子関係だから何でも見える、というわけではないのです。

private が意味すること

private は、そのクラスの内部からだけアクセスできる という指定です。

これは継承している場合でも変わりません。
スーパークラスに private フィールドがある場合、そのフィールドはサブクラスから直接使えません。

たとえば、DemonSlayer クラスに次のようなフィールドがあるとします。

フィールド意味
name隊士の名前
rank階級

これらが private なら、DemonSlayer クラスの中では使えます。
しかし、DemonSlayer を継承した PillarSlayer クラスの中から、name や rank を直接書くことはできません。

private のアクセス範囲

アクセスしたい場所private メンバに直接アクセスできるか
同じクラスの中できる
サブクラスの中できない
別のクラスできない

private は、かなり強く守られた指定です。
継承関係があっても例外ではありません。

↓クリックすると拡大表示されます。

サブクラスから private メンバに直接アクセスできない理由

継承を学び始めると、親クラスのメンバを受け継ぐなら、子クラスから直接見えてもよさそうに感じるかもしれません。

しかし、Javaでは private をかなり厳密に扱います。

private は、
そのクラス自身だけが責任を持って扱う内部情報
という意味を持つからです。

鬼滅の刃風にたとえると、DemonSlayer クラスが持っている name や rank が private の場合、それは鬼殺隊士クラスの中だけで管理される隊士名簿です。

PillarSlayer は DemonSlayer を受け継いでいます。
しかし、親クラスの内部台帳を直接開いて、name や rank を勝手に読み書きすることはできません。

これは不便に見えるかもしれませんが、クラスの安全性を守るためには大切です。

勝手に内部データを変更できてしまうと、

問題内容
不正な値が入る階級にありえない値が入る可能性がある
管理ルールが崩れる本来メソッド経由で行うべき処理を飛ばしてしまう
クラスの責任範囲が曖昧になる誰がその値を管理しているのか分かりにくくなる

このような問題が起こりやすくなります。

そのため、private はカプセル化を守るためにとても重要です。

protected が登場する理由

一方で、スーパークラスとサブクラスは近い関係にあります。

親クラスが持っている情報を、子クラス側でも使いたい場面があります。
そのようなときに使えるのが protected です。

protected を付けたメンバは、サブクラスから直接アクセスできます。

鬼滅の刃風にたとえると、DemonSlayer クラスの基本情報のうち、柱である PillarSlayer にも共有してよい情報を protected にしておくイメージです。

たとえば、次のような情報を子クラスの表示メソッドで使いたいとします。

情報子クラスで使いたい理由
name柱の名前を表示したい
rank柱の階級を表示したい
area柱の担当区域と一緒に表示したい

このような場合、name と rank を protected にしておくと、PillarSlayer の中から直接使えます。

protected は親子で共有しやすい指定

protected は、private と public の中間のような感覚で理解すると分かりやすいです。

修飾子サブクラスから直接アクセスできるかイメージ
privateできない親クラスだけの内部情報
protectedできる子クラスにも共有する情報
publicできる外部にも公開する情報

今回の中心は、private と protected の違いです。

親クラスにあるメンバをサブクラスの中で直接使いたい場合、private ではなく protected が候補になります。

ただし、何でも protected にすればよいわけではありません。
外から守りたい情報なのか、子クラスに共有してよい情報なのかを考えて使い分けることが大切です。

protected メンバを確認する

ファイル名:Sample3.java

class DemonSlayer
{
    protected String name;
    protected String rank;

    public DemonSlayer()
    {
        name = "隊士未登録";
        rank = "階級未設定";
        System.out.println("鬼殺隊士を作成しました。");
    }

    public void setSlayer(String n, String r)
    {
        name = n;
        rank = r;
        System.out.println("名前を" + name + "に、階級を" + rank + "にしました。");
    }

    public void show()
    {
        System.out.println("隊士の名前は" + name + "です。");
        System.out.println("階級は" + rank + "です。");
    }
}

class PillarSlayer extends DemonSlayer
{
    private int area;

    public PillarSlayer()
    {
        area = 0;
        System.out.println("柱クラスの隊士を作成しました。");
    }

    public void setArea(int a)
    {
        area = a;
        System.out.println("担当区域を" + area + "にしました。");
    }

    public void newShow()
    {
        System.out.println("柱の名前は" + name + "です。");
        System.out.println("階級は" + rank + "です。");
        System.out.println("担当区域は" + area + "です。");
    }
}

class Sample3
{
    public static void main(String[] args)
    {
        PillarSlayer slayer1;
        slayer1 = new PillarSlayer();

        slayer1.newShow();
    }
}

Sample3.java で注目するところ

このプログラムでいちばん大事なのは、DemonSlayer クラスの name と rank が protected になっているところです。

protected String name;
protected String rank;

この指定により、サブクラスである PillarSlayer の newShow() メソッドから、name と rank を直接使えます。

newShow() の中では、次の3つを表示しています。

表示している内容定義されている場所
nameスーパークラス DemonSlayer
rankスーパークラス DemonSlayer
areaサブクラス PillarSlayer

つまり、newShow() は親クラスから受け継いだ protected メンバと、子クラスで追加した private メンバの両方を使っています。

public void newShow()
{
    System.out.println("柱の名前は" + name + "です。");
    System.out.println("階級は" + rank + "です。");
    System.out.println("担当区域は" + area + "です。");
}

ここで name と rank を直接使えているのは、protected だからです。

実行結果の例

鬼殺隊士を作成しました。
柱クラスの隊士を作成しました。
柱の名前は隊士未登録です。
階級は階級未設定です。
担当区域は0です。

まず、PillarSlayer オブジェクトを作成すると、継承のルールにより、先に DemonSlayer のコンストラクタが動きます。

鬼殺隊士を作成しました。

そのあと、PillarSlayer のコンストラクタが動きます。

柱クラスの隊士を作成しました。

最後に newShow() が呼ばれ、name、rank、area が表示されます。

このとき、name と rank は親クラス DemonSlayer にあるフィールドです。
しかし protected なので、子クラス PillarSlayer の newShow() から直接参照できています。

もし name と rank が private だったらどうなるか

もし DemonSlayer クラスの name と rank が protected ではなく private だった場合、PillarSlayer の newShow() で次のような書き方はできません。

System.out.println("柱の名前は" + name + "です。");
System.out.println("階級は" + rank + "です。");

なぜなら、private フィールドはスーパークラスの内部だけで使える情報だからです。

サブクラスである PillarSlayer の中からでも、name と rank を直接読むことはできません。

違いを整理すると、次のようになります。

スーパークラスのフィールド指定サブクラスから直接使えるか
private String name使えない
protected String name使える

この1行の違いが、継承におけるメンバアクセスではとても大きな差になります。

newShow() が意味していること

newShow() は、PillarSlayer 専用の表示メソッドです。

このメソッドでは、柱としての担当区域だけでなく、親クラスにある名前や階級も一緒に表示しています。

鬼滅の刃風に考えると、柱を紹介するときには、次の情報をまとめて見たい場面があります。

表示したい情報内容
名前どの隊士なのか
階級どの立場なのか
担当区域どこを任されているのか

このとき、親クラスの name と rank に子クラスから触れられないと、PillarSlayer 専用の表示が作りにくくなります。

そこで protected が役立ちます。

protected は、
親子関係の中で必要な情報を共有しやすくするための指定
と考えると分かりやすいです。

実行の流れを整理する

Sample3.java の流れを順番に整理すると、次のようになります。

順番処理内容
1PillarSlayer slayer1;PillarSlayer 型の変数を用意する
2slayer1 = new PillarSlayer();オブジェクトを作成する
3DemonSlayer()親クラス側の name と rank を初期化する
4PillarSlayer()子クラス側の area を初期化する
5slayer1.newShow();name、rank、area を表示する

この流れの中で特に大事なのは、newShow() です。

newShow() では、親クラスの protected メンバである name と rank に直接アクセスしています。
さらに、子クラス自身の private メンバである area にもアクセスしています。

つまり、PillarSlayer の中では、

メンバ直接アクセスできる理由
name親クラスで protected だから
rank親クラスで protected だから
area自分のクラスの private メンバだから

という関係になっています。

図:private と protected の違い

↓クリックすると拡大表示されます。

この図が示していること

この図では、DemonSlayer クラスにある private メンバと protected メンバが、PillarSlayer クラスからどう見えるかを表しています。

private メンバから PillarSlayer への矢印にはバツが付いています。
これは、サブクラスであっても private メンバには直接アクセスできないことを示しています。

一方、protected メンバから PillarSlayer へは青い矢印が届いています。
これは、protected メンバならサブクラスから直接使えることを示しています。

この図から分かることは、継承しているかどうかだけでアクセスが決まるわけではない、という点です。
アクセスできるかどうかは、メンバに付いているアクセス修飾子によって決まります。

カプセル化と protected のバランス

private は、情報を強く守るための指定です。
そのクラスの内部だけでデータを管理したい場合に向いています。

一方、protected は、サブクラスにも利用させたい情報がある場合に使えます。
親クラスと子クラスの連携をしやすくする指定です。

ただし、protected は便利だからといって、何でも付ければよいわけではありません。

指定向いている場面
privateそのクラスの中だけで厳密に管理したい
protectedサブクラスにも利用させたい
public外部から広く使わせたい

鬼滅の刃風にたとえると、DemonSlayer の内部で厳重に管理したい情報は private にします。
一方で、PillarSlayer にも使わせたい基本情報は protected にします。

つまり、設計するときには、次の視点が大切です。

視点考えること
守るべき情報か外から直接触れないようにする
子クラスで使う必要があるかprotected を検討する
外部に公開してよいかpublic を検討する

アクセス修飾子は、ただの文法ではありません。
クラスの責任範囲を決める設計の道具です。

private と protected をどう使い分けるか

private と protected の使い分けは、次のように考えると分かりやすいです。

使い分けの基準向いている指定
そのクラスだけで管理したいprivate
サブクラスでも直接使いたいprotected
どこからでも使わせたいpublic

今回の Sample3.java では、name と rank を PillarSlayer の newShow() で直接表示したかったため、protected にしています。

もし、親クラスだけで値を管理し、子クラスからは専用メソッド経由で使わせたいなら、private のままにする方法もあります。

たとえば、次のような考え方です。

設計方針
子クラスから直接使わせるprotected String name
子クラスから直接使わせないprivate String name と getName() のようなメソッド

どちらが正しいというより、何をどこまで見せたいかによって選びます。

protected は同じパッケージからもアクセスできる

protected には、もうひとつ覚えておきたい特徴があります。

protected はサブクラスからアクセスできるだけでなく、同じパッケージにあるクラスからもアクセスできます。

ただし、今回の中心は継承におけるアクセスです。
まずは、次の理解をしっかり押さえておけば大丈夫です。

修飾子継承で重要なポイント
privateサブクラスから直接アクセスできない
protectedサブクラスから直接アクセスできる

同じパッケージからのアクセスについては、パッケージを学ぶときにあらためて整理すると理解しやすいです。

図:DemonSlayer と PillarSlayer のメンバアクセス

↓クリックすると拡大表示されます。

この図が示していること

この図では、PillarSlayer の newShow() がどのメンバを使っているかを表しています。

DemonSlayer の name と rank から newShow() に矢印が伸びています。
これは、name と rank が protected なので、サブクラスから直接使えることを示しています。

また、PillarSlayer の area から newShow() にも矢印が伸びています。
area は PillarSlayer 自身の private メンバなので、同じ PillarSlayer クラスの中にある newShow() からは直接使えます。

つまり、newShow() は、

メンバどこにあるかなぜ使えるか
nameDemonSlayerprotected だから
rankDemonSlayerprotected だから
areaPillarSlayer同じクラスの private メンバだから

という関係で情報を表示しています。

鬼滅の刃風に private と protected を整理する

DemonSlayer は、鬼殺隊士という共通の土台です。
そこには、名前や階級のような基本情報があります。

PillarSlayer は、その土台を受け継いだ柱の隊士です。
柱として担当区域を持ち、さらに自分専用の表示メソッド newShow() も持っています。

このとき、親クラスの情報をどこまで子クラスに見せるかを決めるのがアクセス修飾子です。

アクセス修飾子鬼滅の刃風のイメージ
private鬼殺隊士クラスの内部だけで管理する秘密情報
protected柱クラスにも共有してよい基本情報
public外部からも使える公開された機能

継承しているから全部見えるのではありません。
見せる範囲をアクセス修飾子で調整することが大切です。

継承とカプセル化は一緒に考える

継承は、親クラスの機能を子クラスに受け継がせる仕組みです。
一方、カプセル化は、クラスの内部情報を守る考え方です。

一見すると、継承は情報を広げる仕組みで、カプセル化は情報を隠す仕組みに見えるかもしれません。
でも、実際にはこの2つは一緒に使うことで設計が整います。

考え方役割
継承共通部分を子クラスに受け継がせる
カプセル化内部情報を勝手に触られないように守る
protected親子間で必要な情報を共有する
private親クラス内部だけで情報を守る

鬼滅の刃風にたとえると、鬼殺隊の共通ルールは子クラスにも受け継がせます。
しかし、隊士名簿の中でも厳重に守るべき情報は private にして、子クラスにも直接触らせません。

一方で、柱クラスでも使う必要がある情報は protected にして、親子で連携しやすくします。

このように、継承とカプセル化は対立するものではありません。
守る情報と共有する情報を分けることで、より分かりやすく安全なクラス設計になります。

この内容で押さえておきたいポイント

ポイント内容
継承してもすべてに直接アクセスできるわけではないアクセス修飾子によって見える範囲が変わる
private同じクラスの中からだけ直接アクセスできる
protectedサブクラスから直接アクセスできる
サブクラス内の privateそのサブクラス内では直接使える
protected の使いどころ親クラスの情報を子クラスでも使いたいとき
private の使いどころ親クラス内部だけで厳密に守りたいとき
設計の考え方何を守り、何を子クラスに共有するかを決める

private と protected を理解すると、継承の中での情報の見え方がはっきりします。

親クラスから受け継ぐけれど、何でも直接触れるわけではない。
この感覚を持つと、Javaのクラス設計がより安全で整理されたものになります。