Java超|継承におけるメンバアクセス

親子クラスでも、すべての情報を自由に見られるわけではありません。private と protected を理解すると、継承の中で守る情報と子クラスへ共有する情報の境界線がはっきり見えてきます。

Javaの継承では、スーパークラスの性質や機能をサブクラスが受け継げます。

たとえば、SaiyanWarrior という親クラスを作り、そこから SuperSaiyanWarrior という子クラスを作るとします。

SaiyanWarrior には、サイヤ人戦士として共通する情報を持たせます。

名前。
流派名。
戦闘力。
状態を表示するメソッド。
名前や流派を設定するメソッド。

そこから SuperSaiyanWarrior を作ると、スーパーサイヤ人としての変身段階や追加能力を持たせられます。

ただし、ここで大切なのは、継承したからといって、親クラスのすべてのメンバに子クラスから自由に直接アクセスできるわけではないという点です。

Javaでは、フィールドやメソッドにアクセス修飾子を付けることで、どこから使えるかを決めます。

今回の中心になるのは、private と protected です。

ドラゴンボールの世界観でたとえると、SaiyanWarrior クラスは、サイヤ人戦士としての共通情報を持つ親クラスです。

そこから SuperSaiyanWarrior クラスという、変身段階を持つ子クラスを作ります。

スーパーサイヤ人はサイヤ人戦士の一種なので、サイヤ人戦士としての情報を受け継ぎます。

けれども、親クラスが内部だけで厳重に守りたい情報まで、子クラスが勝手に直接触れるわけではありません。

たとえば、戦士管理本部の台帳には次のような情報があります。

情報の種類ドラゴンボール風のイメージアクセス修飾子
親クラスだけで厳重に管理したい情報戦士台帳の奥にある内部管理情報private
子クラスにも使わせたい情報スーパーサイヤ人側にも共有される基本情報protected

つまり、継承では、親から子へ受け継ぐことと、子から直接触れることを分けて考える必要があります。

この境界線を作るのが private と protected です。

この記事では、継承におけるメンバアクセスのしくみを、Sample3.java を使って、ドラゴンボールの世界観で解説します。特に、protected にした親クラスのフィールドを子クラスから直接使えること、private にした子クラス自身のフィールドは同じ子クラス内から使えること、そして private と protected の使い分けを丁寧に整理します。

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

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

ここはとても大事です。

サブクラスはスーパークラスの性質を受け継ぎます。

しかし、実際にそのメンバへ直接アクセスできるかどうかは、アクセス修飾子によって決まります。

たとえば、親クラスにあるフィールドが private なら、そのフィールドは親クラスの内部だけで使える情報になります。

サブクラスであっても、その private フィールドを直接読むことはできません。

ドラゴンボール風にたとえると、SuperSaiyanWarrior は SaiyanWarrior を受け継いだ存在です。

しかし、SaiyanWarrior の内部保管庫にある秘密の戦士台帳を、SuperSaiyanWarrior が勝手に開けることはできません。

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

考え方正しい理解
継承したら全部直接使えるそうではない
使えるかどうかは何で決まるかアクセス修飾子で決まる
private メンバサブクラスから直接使えない
protected メンバサブクラスから直接使える

この違いが分かると、継承とカプセル化の関係が見えやすくなります。

private が意味すること

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

これは継承している場合でも変わりません。

スーパークラスに private フィールドがある場合、そのフィールドはサブクラスから直接使えません。

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

フィールド意味
name戦士の名前
styleName流派名

これらが private なら、SaiyanWarrior クラスの中では使えます。

しかし、SaiyanWarrior を継承した SuperSaiyanWarrior クラスの中から、name や styleName を直接書くことはできません。

private のアクセス範囲を表にすると、次のようになります。

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

private は、かなり強く守られた指定です。

継承関係があっても例外ではありません。

ドラゴンボール風にたとえると、private は戦士管理本部の奥にある内部台帳です。

親クラス自身だけが、その台帳を直接開いて操作できます。

スーパーサイヤ人クラスは親クラスを受け継いでいますが、親クラスの奥にある private 台帳を直接開くことはできません。

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

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

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

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

ドラゴンボール風にたとえると、SaiyanWarrior クラスが持っている name や styleName が private の場合、それはサイヤ人戦士クラスの中だけで管理される戦士台帳です。

SuperSaiyanWarrior は SaiyanWarrior を受け継いでいます。

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

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

勝手に内部データを変更できてしまうと、次のような問題が起こりやすくなります。

問題内容
不正な値が入る流派名に存在しない値が入る可能性がある
管理ルールが崩れる本来メソッド経由で行うべき処理を飛ばしてしまう
クラスの責任範囲が曖昧になる誰がその値を管理しているのか分かりにくくなる
保守しにくくなる後から仕様変更したときに影響範囲が広がる

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

図:private は親クラス内部だけの情報

この図が示していること

この図では、SaiyanWarrior クラスにある private メンバが、SuperSaiyanWarrior クラスから直接見えないことを表しています。

SuperSaiyanWarrior は SaiyanWarrior を継承しています。

しかし、private name や private styleName は、親クラス内部だけで扱う情報です。

そのため、子クラスから直接アクセスする矢印にはバツ印が付いています。

ここから分かるのは、継承しているかどうかだけでアクセスが決まるわけではないということです。

アクセスできるかどうかは、メンバに付いているアクセス修飾子によって決まります。

protected が登場する理由

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

親クラスが持っている情報を、子クラス側でも使いたい場面があります。

そのようなときに使えるのが protected です。

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

ドラゴンボール風にたとえると、SaiyanWarrior クラスの基本情報のうち、SuperSaiyanWarrior にも共有してよい情報を protected にしておくイメージです。

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

情報子クラスで使いたい理由
nameスーパーサイヤ人の名前を表示したい
styleNameどの流派の戦士か表示したい
transformationLevel変身段階と一緒に表示したい

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

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

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

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

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

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

ただし、何でも protected にすればよいわけではありません。

外から守りたい情報なのか、子クラスに共有してよい情報なのかを考えて使い分けることが大切です。

ドラゴンボール風に言うと、戦士管理本部の情報には、次のような違いがあります。

情報ふさわしい扱い
本部だけが管理する秘密情報private
スーパーサイヤ人クラスにも使わせたい基本情報protected
外部から呼び出してよい操作public

アクセス修飾子は、ただ文法として付けるものではありません。

クラスの責任範囲と、情報の見せ方を決める設計の道具です。

protected メンバを確認する

ファイル名:Sample3.java

class SaiyanWarrior
{
    protected String name;
    protected String styleName;

    public SaiyanWarrior()
    {
        name = "戦士未登録";
        styleName = "流派未設定";
        System.out.println("サイヤ人戦士を作成しました。");
    }

    public void setWarrior(String n, String s)
    {
        name = n;
        styleName = s;
        System.out.println("名前を" + name + "に、流派を" + styleName + "にしました。");
    }

    public void show()
    {
        System.out.println("戦士の名前は" + name + "です。");
        System.out.println("流派は" + styleName + "です。");
    }
}

class SuperSaiyanWarrior extends SaiyanWarrior
{
    private int transformationLevel;

    public SuperSaiyanWarrior()
    {
        transformationLevel = 0;
        System.out.println("スーパーサイヤ人クラスの戦士を作成しました。");
    }

    public void setTransformationLevel(int level)
    {
        transformationLevel = level;
        System.out.println("変身段階を" + transformationLevel + "にしました。");
    }

    public void newShow()
    {
        System.out.println("スーパーサイヤ人の名前は" + name + "です。");
        System.out.println("流派は" + styleName + "です。");
        System.out.println("変身段階は" + transformationLevel + "です。");
    }
}

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

        warrior1.newShow();
    }
}

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

protected String name;
protected String styleName;

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

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

表示している内容定義されている場所
nameスーパークラス SaiyanWarrior
styleNameスーパークラス SaiyanWarrior
transformationLevelサブクラス SuperSaiyanWarrior

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

public void newShow()
{
    System.out.println("スーパーサイヤ人の名前は" + name + "です。");
    System.out.println("流派は" + styleName + "です。");
    System.out.println("変身段階は" + transformationLevel + "です。");
}

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

Sample3.java の実行結果の例

サイヤ人戦士を作成しました。
スーパーサイヤ人クラスの戦士を作成しました。
スーパーサイヤ人の名前は戦士未登録です。
流派は流派未設定です。
変身段階は0です。

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

サイヤ人戦士を作成しました。

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

スーパーサイヤ人クラスの戦士を作成しました。

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

このとき、name と styleName は親クラス SaiyanWarrior にあるフィールドです。

しかし protected なので、子クラス SuperSaiyanWarrior の newShow() から直接参照できています。

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

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

System.out.println("スーパーサイヤ人の名前は" + name + "です。");
System.out.println("流派は" + styleName + "です。");

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

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

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

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

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

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

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

このメソッドでは、スーパーサイヤ人としての変身段階だけでなく、親クラスにある名前や流派名も一緒に表示しています。

ドラゴンボール風に考えると、スーパーサイヤ人戦士を紹介するときには、次の情報をまとめて見たい場面があります。

表示したい情報内容
名前どの戦士なのか
流派どの流派の戦士なのか
変身段階どの段階まで覚醒しているのか

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

そこで protected が役立ちます。

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

Sample3.java の流れを整理する

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

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

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

newShow() では、親クラスの protected メンバである name と styleName に直接アクセスしています。

さらに、子クラス自身の private メンバである transformationLevel にもアクセスしています。

つまり、SuperSaiyanWarrior の中では、次のような関係になっています。

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

ここが、継承におけるメンバアクセスの大切なポイントです。

図:private と protected の違い

この図が示していること

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

newShow() から private secretPower への矢印にはバツ印が付いています。

これは、サブクラスであっても private メンバには直接アクセスできないことを示しています。

一方、newShow() から protected name と protected styleName へは青い矢印が届いています。

これは、protected メンバならサブクラスから直接使えることを示しています。

さらに、transformationLevel は SuperSaiyanWarrior 自身の private メンバなので、同じ SuperSaiyanWarrior クラスの中にある newShow() から直接使えます。

この図から分かることは、継承しているかどうかだけでアクセスが決まるわけではないという点です。

アクセスできるかどうかは、メンバに付いているアクセス修飾子によって決まります。

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

private は、情報を強く守るための指定です。

そのクラスの内部だけでデータを管理したい場合に向いています。

一方、protected は、サブクラスにも利用させたい情報がある場合に使えます。

親クラスと子クラスの連携をしやすくする指定です。

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

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

ドラゴンボール風にたとえると、SaiyanWarrior の内部で厳重に管理したい情報は private にします。

一方で、SuperSaiyanWarrior にも使わせたい基本情報は protected にします。

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

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

アクセス修飾子は、ただの文法ではありません。

クラスの責任範囲を決める設計の道具です。

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

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

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

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

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

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

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

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

ドラゴンボール風に言うと、戦士名や流派名をスーパーサイヤ人側の表示で直接使いたいなら protected が便利です。

一方、戦闘力補正や内部ランクのように、親クラスの中だけで厳密に管理したい情報なら private のほうが安全です。

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

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

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

ただし、今回の中心は継承におけるアクセスです。

まずは、次の理解をしっかり押さえておけば大丈夫です。

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

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

ここではまず、親クラスの protected メンバは子クラスの中から直接使える、ということを中心に押さえます。

図:SaiyanWarrior と SuperSaiyanWarrior のメンバアクセス

この図が示していること

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

newShow() から SaiyanWarrior の name と styleName に矢印が伸びています。

これは、name と styleName が protected なので、サブクラスから直接使えることを示しています。

また、newShow() から SuperSaiyanWarrior の transformationLevel にも矢印が伸びています。

transformationLevel は SuperSaiyanWarrior 自身の private メンバなので、同じ SuperSaiyanWarrior クラスの中にある newShow() からは直接使えます。

つまり、newShow() は次の関係で情報を表示しています。

メンバどこにあるかなぜ使えるか
nameSaiyanWarriorprotected だから
styleNameSaiyanWarriorprotected だから
transformationLevelSuperSaiyanWarrior同じクラスの private メンバだから

ここから分かるのは、子クラスのメソッドは、親クラスの protected メンバと、自分自身の private メンバを組み合わせて処理を作れるということです。

ドラゴンボール風に private と protected を整理する

SaiyanWarrior は、サイヤ人戦士という共通の土台です。

そこには、名前や流派名のような基本情報があります。

SuperSaiyanWarrior は、その土台を受け継いだスーパーサイヤ人戦士です。

スーパーサイヤ人として変身段階を持ち、さらに自分専用の表示メソッド newShow() も持っています。

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

アクセス修飾子ドラゴンボール風のイメージ
privateサイヤ人戦士クラスの内部だけで管理する秘密情報
protectedスーパーサイヤ人クラスにも共有してよい基本情報
public外部からも使える公開された機能

継承しているから全部見えるのではありません。

見せる範囲をアクセス修飾子で調整することが大切です。

たとえば、名前や流派名を子クラス側の表示でも使いたいなら protected にする選択があります。

一方、戦闘力の内部補正値や秘密の判定値のように、親クラスだけで守りたい情報なら private にするほうが安全です。

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

継承は、親クラスの機能を子クラスに受け継がせる仕組みです。

一方、カプセル化は、クラスの内部情報を守る考え方です。

一見すると、継承は情報を広げる仕組みで、カプセル化は情報を隠す仕組みに見えるかもしれません。

でも、実際にはこの2つは一緒に使うことで設計が整います。

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

ドラゴンボール風にたとえると、サイヤ人戦士の共通ルールは子クラスにも受け継がせます。

しかし、戦士台帳の中でも厳重に守るべき情報は private にして、子クラスにも直接触らせません。

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

このように、継承とカプセル化は対立するものではありません。

守る情報と共有する情報を分けることで、より分かりやすく安全なクラス設計になります。

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

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

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

親クラスから受け継ぐけれど、何でも直接触れるわけではありません。

この感覚を持つと、Javaのクラス設計がより安全で整理されたものになります。