Java入門|継承におけるメンバアクセスのしくみ(privateとprotected)

親子の関係でも、何でも自由に見えるわけではない。
private と protected を知ると、継承の中で守るべき境界線がはっきり見えてくる。

継承を学ぶと、スーパークラスのメンバはサブクラスに受け継がれる、という考え方が見えてきます。
ここで次に気になるのが、受け継いだメンバにどこまでアクセスできるのか、という点です。

親クラスから子クラスへ性質や機能を引き継げるのは、継承の大きな強みです。けれども、受け継がれるからといって、すべてのメンバをサブクラスから自由に直接使えるわけではありません。Javaでは、メンバにどこからアクセスできるかを、アクセス修飾子で細かくコントロールできます。

ドラゴンボールでたとえると、これは戦士の情報管理に少し似ています。
たとえば、サイヤ人の基本情報をまとめたスーパークラスがあり、その情報を受け継ぐエリートサイヤ人のサブクラスがあるとします。親子のように近い関係であっても、見せてよい情報と、直接は触らせない情報があります。

  • 完全に親クラスの内部だけで守りたい情報
  • 子クラスなら使ってよい情報

この違いを表すのが、private と protected です。

今回は、継承におけるメンバアクセスのしくみを、ドラゴンボールの戦士たちに置きかえながら丁寧に整理していきます。特に、

  • private メンバにはサブクラスから直接アクセスできないこと
  • protected メンバならサブクラスからアクセスできること
  • カプセル化を守りながら、継承で柔軟に設計する考え方

をしっかり見ていきます。

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

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

ここはとても大事です。

継承では、スーパークラスの性質や機能をサブクラスが受け継ぎます。
ただし、アクセスの可否は、各メンバに付いているアクセス修飾子によって決まります。

たとえばスーパークラスにあるフィールドが private なら、そのフィールドはスーパークラスの内部だけで守られます。サブクラスであっても、外側のクラスとして扱われるため、直接は触れません。

ドラゴンボール風に言うと、サイヤ人の土台クラスにある「戦士の内部管理情報」が private なら、エリートサイヤ人であってもその情報を直接のぞき込むことはできない、ということです。

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

private が意味すること

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

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

たとえば、サイヤ人クラスに次のようなフィールドがあったとします。

フィールド意味
name戦士の名前
power戦闘力

これらが private なら、サイヤ人クラス自身の中では使えますが、そこから派生したエリートサイヤ人クラスの中からは、そのまま name や power を直接書くことはできません。

これは不便に見えるかもしれませんが、カプセル化の観点ではとても大切です。
なぜなら、クラスの内部データを勝手に直接触られないようにすることで、誤った変更や設計の崩れを防げるからです。

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

継承していると、つい
「親のものを受け継いでいるのだから、子から直接見えてもよさそう」
と感じるかもしれません。

でも Java はそこを慎重に扱っています。

private は
そのクラス自身だけの秘密の情報
という位置づけです。

ドラゴンボールでたとえると、サイヤ人クラスが持っている内部管理の情報は、サイヤ人という設計図の中で責任を持って扱うものです。エリートサイヤ人はその仕組みを土台として使ってはいますが、親クラスの内部保管庫を勝手に開けられるわけではありません。

だから、サブクラスで新しい表示メソッドを作ったとしても、スーパークラスの private フィールドを直接表示しようとするとエラーになります。

private のときのイメージ

イメージを表で整理するとわかりやすいです。

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

この表からわかるように、private はかなり強く守られた指定です。

継承関係があっても例外ではありません。
ここが、public や protected と大きく違う点です。

protected が登場する理由

スーパークラスとサブクラスは、とても近い関係にあります。
そのため、親クラスのメンバをサブクラス側で少し柔軟に扱いたい場面も出てきます。

そこで使えるのが protected です。

protected を付けたメンバは、private とは違って、サブクラスからアクセスできます。
つまり、親クラスの中だけでなく、そこを継承した子クラスの中でも使えるようになります。

ドラゴンボールでたとえるなら、サイヤ人クラスの基本情報のうち、エリートサイヤ人にも見せてよい範囲の情報を protected にしておくイメージです。

たとえば、

  • name(戦士の名前)
  • power(戦闘力)

といった情報を、子クラスの表示機能でも活用したいなら、protected にしておくと設計しやすくなります。

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

protected は、クラスの内部だけで閉じる private と、どこからでも見える public の中間のような感覚で理解するとわかりやすいです。

特に継承では、次の特徴が重要です。

修飾子サブクラスから直接アクセス
privateできない
protectedできる
publicできる

このうち今回の中心になるのは、private と protected の違いです。

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

サンプルプログラム:protected メンバにサブクラスからアクセス

ここでは、ドラゴンボールの世界観のプログラムで、protected メンバにサブクラスからアクセスできる様子を確認していきます。内容は、サイヤ人クラスの基本情報を、エリートサイヤ人クラスが直接表示できる構成です。

ファイル名:Sample3.java

class Saiyan
{
    protected String name;
    protected int power;

    public Saiyan()
    {
        name = "戦士なし";
        power = 0;
        System.out.println("サイヤ人を作成しました。");
    }

    public void setSaiyan(String n, int p)
    {
        name = n;
        power = p;
        System.out.println("名前を" + name + "に戦闘力を" + power + "にしました。");
    }

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

class EliteSaiyan extends Saiyan
{
    private int area;

    public EliteSaiyan()
    {
        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("戦闘力は" + power + "です。");
        System.out.println("担当エリアは" + area + "です。");
    }
}

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

        warrior1.newShow();
    }
}

このプログラムで注目するところ

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

protected String name;
protected int power;

この指定により、サブクラスである EliteSaiyan の newShow() メソッドの中から、name と power を直接使えるようになっています。

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

表示している内容どこに定義されているか
nameスーパークラス Saiyan
powerスーパークラス Saiyan
areaサブクラス EliteSaiyan

つまりこのメソッドは、

  • 親から受け継いだ protected メンバ
  • 子クラスで追加した private メンバ

の両方を使って表示しています。

これが protected の使いどころです。

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

ここで比較がとても重要です。

もし Saiyan クラスの name と power が protected ではなく private だったら、EliteSaiyan の newShow() で次のような記述はできません。

System.out.println("エリートサイヤ人の名前は" + name + "です。");
System.out.println("戦闘力は" + power + "です。");

なぜなら、その2つは親クラスの内部専用メンバになってしまうからです。

つまり、サブクラスの中にいても、

  • name
  • power

を直接読み取れません。

この違いを表にすると、とても整理しやすいです。

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

この1行の違いが、継承の設計ではかなり大きな差になります。

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

newShow() は、エリートサイヤ人専用の表示メソッドです。
このメソッドでは、エリート戦士としての担当エリアだけでなく、親クラスにある戦士名や戦闘力も一緒に表示しています。

ドラゴンボールで考えると、これはとても自然です。

エリートサイヤ人を紹介するなら、

  • その戦士の名前
  • 戦闘力
  • 担当エリア

をまとめて見たい場面があります。

そのとき、親クラスの情報に子クラスから触れられないと不便です。
こうした場面で protected が役立ちます。

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

実行の流れを整理する

このプログラムの流れを順番に見ると、次のようになります。

順番処理内容
1new EliteSaiyan()先に Saiyan()、次に EliteSaiyan() が動く
2warrior1.newShow()親の name と power、子の area を表示する

最初のオブジェクト作成では、継承のルールどおり、まずスーパークラス側の初期化が行われます。
そのあとでサブクラス側の初期化が行われます。

そして newShow() の中で、protected にしておいた親クラスのメンバにアクセスできるため、まとめて状態を表示できるようになっています。

図で private と protected の違いを整理する

この図では、Saiyan クラスのメンバが2つの扱いに分かれている様子を見ます。

上側の private は、親クラスの外へ出ない情報です。
そのため、右側の EliteSaiyan クラスへ向かう矢印にはバツが付きます。

下側の protected は、継承したサブクラスなら使える情報です。
そのため、EliteSaiyan クラスへ矢印が届いています。

この図から、継承の関係があっても access のルールはきちんと区別されていることがわかります。

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

ここで考えておきたいのが、カプセル化との関係です。

private は情報を強く守る指定です。
そのため、安全性を高めたいときに有効です。
一方で、継承したサブクラスからも直接使えないため、設計によっては少し窮屈になることがあります。

protected は、その窮屈さをやわらげます。
スーパークラスとサブクラスのあいだで、必要な情報を共有しやすくなるからです。

ただし、使いやすいからといって、何でも protected にすればよいわけではありません。
本当にサブクラス側から触る必要があるメンバだけに使う、という意識が大切です。

ドラゴンボールで言えば、

  • 完全に親クラスの内部だけで管理したい情報は private
  • 子クラスにも見せて連携させたい情報は protected

と考えると自然です。

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

使い分けの考え方を整理すると、次のようになります。

使い分けの基準向いている指定
そのクラスの中だけで厳密に管理したいprivate
サブクラスにも利用させたいprotected

今回の内容では、name と power を子クラスの表示に使いたかったため、protected が合っています。

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

つまり大事なのは、
何をどこまで見せる設計にしたいか
です。

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

Java の protected には、もうひとつ覚えておきたい性質があります。
それは、サブクラスだけでなく、同じパッケージに属するクラスからもアクセスできることです。

今回の中心は継承におけるアクセスなので、まずは
protected はサブクラスからアクセスできる
という理解をしっかり固めれば十分です。

そのうえで、Java ではさらに同じパッケージからもアクセス可能になる、という特徴があることを覚えておくと、あとで整理しやすくなります。

ドラゴンボールで感覚的に整理する

最後に、ドラゴンボールでの感覚で整理してみましょう。

Saiyan クラスは、サイヤ人という共通の戦士の土台です。
そこには、名前や戦闘力のような基本情報があります。

EliteSaiyan クラスは、その土台を受け継いだ特別な戦士です。
ここでは担当エリアという独自要素が追加されています。

このとき、

  • 親クラスだけが管理すべき秘密の情報なら private
  • 子クラスにも見せて使わせたい情報なら protected

という使い分けになります。

つまり、

継承しているから全部見えるのではなく、見せる範囲を修飾子で調整する

というのが、継承におけるメンバアクセスの基本です。

この考え方がわかると、継承とカプセル化が対立するものではなく、むしろ一緒に設計を整えるための仕組みだと見えてきます。