protectedの基本:継承クラスのためのアクセス許可

docs

前にpublicprivateといったアクセス修飾子について学んできましたね。

アクセス修飾子:publicとprivateで情報を守ろう | ToolDocs

今回は、それらのちょうど中間に位置する「protected」アクセス修飾子について、継承との関係を中心に深掘りしていきます。

 

「private」と「public」の間に立つ「protected」

前回、privateは「自分だけの秘密」、publicは「誰にでもオープン」という話をしました。じゃあ、protectedって何者?

protectedは、ざっくり言うと「自分自身と、自分を継承したクラス(子クラス)にはアクセスを許可するけど、それ以外の全然関係ないクラスにはアクセスさせない」という修飾子です。

図で表すとこんな感じです。

+------------------+       +------------------+
|    親クラス      |       |  関係ないクラス  |
| (Parent Class)   |       | (Other Class)    |
|                  |       |                  |
| protected メンバ | <----- アクセスOK!      |
|                  |       |                  |
+-------+----------+       +-------+----------+
        |                          |
        | 継承                     |
        |                          |
+-------V----------+               X アクセスNG!
|    子クラス      |
| (Child Class)    |
|                  |
| protected メンバ | <----- アクセスOK!
|                  |
+------------------+

なんとなくイメージできましたか?

具体例で見てみよう

言葉だけだとフワッとしちゃうので、実際のコードで見ていきましょう。

動物を表現するAnimalクラスと、それを継承するDogクラスを考えます。

Java

// 親クラス:Animal
class Animal {
    String name;
    protected int age; // protectedなフィールド
    private String species; // privateなフィールド

    public Animal(String name, int age, String species) {
        this.name = name;
        this.age = age;
        this.species = species;
        System.out.println(this.name + "が生まれた!");
    }

    public void introduce() {
        System.out.println("私の名前は" + name + "、" + age + "歳です。" + species + "です。");
    }

    // privateなメソッドはAnimalクラス内からしか呼べない
    private void secretMethod() {
        System.out.println("これはAnimalの秘密のメソッドだよ。");
    }

    // publicなメソッドからはsecretMethodを呼べる
    public void callSecretMethod() {
        secretMethod();
    }
}

// 子クラス:Dog
class Dog extends Animal {
    String breed;

    public Dog(String name, int age, String species, String breed) {
        // 親クラスのコンストラクタを呼び出す
        super(name, age, species);
        this.breed = breed;
        System.out.println("犬種の" + this.breed + "だよ。");
    }

    public void bark() {
        // 子クラスから親クラスのprotectedフィールド 'age' にアクセスできる!
        System.out.println(name + "(" + age + "歳)がワンワン!");

        // 親クラスのpublicなメソッドも呼べる
        introduce();

        // privateなフィールドやメソッドにはアクセスできない
        // System.out.println(species); // エラー!
        // secretMethod(); // エラー!
    }
}

// 全然関係ないクラス
class Zoo {
    public static void main(String[] args) {
        Animal myAnimal = new Animal("パンダ", 5, "哺乳類");
        Dog myDog = new Dog("ポチ", 3, "犬", "柴犬");

        // publicなフィールドやメソッドにはアクセスできる
        System.out.println(myAnimal.name); // publicではないけど、同じパッケージなのでアクセスできる
        myAnimal.introduce();
        myDog.bark();

        // protectedなフィールド 'age' には直接アクセスできない
        // System.out.println(myAnimal.age); // エラー!

        // privateなフィールドやメソッドにはもちろんアクセスできない
        // System.out.println(myAnimal.species); // エラー!
        // myAnimal.secretMethod(); // エラー!
    }
}

このコードでは、Animalクラスのageというフィールドをprotectedにしています。

  • Dogクラスの中からは、protectedageにアクセスできていますよね(System.out.println(name + "(" + age + "歳)がワンワン!");のところ)。これはDogAnimalを継承しているからです。
  • 一方、ZooクラスのmainメソッドからmyAnimal.ageに直接アクセスしようとするとエラーになります。なぜなら、ZooクラスはAnimalクラスを継承していない、「関係ないクラス」だからです。

このように、protected継承関係にあるクラスに対してのみ、アクセスを許可するという特徴を持っています。

vscodeで実行した結果


protectedの少し複雑な側面:パッケージ内アクセス

Javaのprotectedは、実はもう少しだけ複雑なルールがあります。それは、同じパッケージ内であれば、継承関係がなくてもアクセスできるという点です。

「え、ややこしい!」と思うかもしれませんが、これはJavaの設計思想によるものです。同じパッケージ内のクラスは、ある程度「仲間」とみなされるんですね。

さっきの例で言うと、AnimalDogZooの3つのクラスが全て同じパッケージ(例:com.moritama.animal)にある場合を考えてみましょう。

この場合、ZooクラスからmyAnimal.ageprotectedなフィールド)にアクセスしてもエラーになりません。

Java

// 同じパッケージ内にあるとする
package com.moritama.animal;

class Animal {
    String name;
    protected int age; // protectedなフィールド
    // ...
}

class Dog extends Animal {
    // ...
}

class Zoo {
    public static void main(String[] args) {
        Animal myAnimal = new Animal("パンダ", 5, "哺乳類");
        // protectedなageにアクセスできる(同じパッケージ内なので)
        System.out.println("パンダの年齢は" + myAnimal.age + "歳です。"); // エラーにならない!
    }
}

ただし、これは**「同じパッケージ内」という条件付き**です。もしZooクラスだけが別のパッケージ(例:com.moritama.anotherpackage)にあったら、myAnimal.ageへのアクセスはエラーになります。

まとめると

  • 自分自身: アクセスOK
  • 子クラス: アクセスOK(別のパッケージでもOK)
  • 同じパッケージ内の関係ないクラス: アクセスOK
  • 別のパッケージの関係ないクラス: アクセスNG

これがprotectedの完全なアクセスルールです。初心者のうちは、「基本的には継承した子クラスのためのもの」と覚えておけばOKです。同じパッケージの話は、そういうパターンもあるんだな、くらいで大丈夫ですよ。


どんな時にprotectedを使うの?

「結局、protectedってどんな時に使うの?」という疑問が出てくるかもしれませんね。

protectedは、主に以下のような場面で活躍します。

  1. 子クラスにだけ設定を変更させたいが、外部からは触られたくない場合 親クラスで定義したメソッドやフィールドを、子クラスでカスタマイズしたり、情報を参照したりさせたいが、外部の全く関係ないクラスからは触られたくない場合に便利です。例えば、親クラスが持つ内部状態のフィールドをprotectedにして、子クラスでその状態に応じて処理を分岐させる、といったケースです。
  2. フレームワークやライブラリ開発で、継承による拡張を前提としている場合 あなたが作ったクラスを、他の開発者が継承して利用することを想定している場合です。継承する側(子クラスを作る側)にとって、親クラスの特定のメソッドやフィールドにアクセスできると便利な場合があります。しかし、publicにしてしまうと、継承しない外部からも自由にアクセスできてしまうため、意図しない使われ方をされるリスクがあります。そこでprotectedを使うことで、継承による拡張は許可しつつ、それ以外の自由なアクセスは制限できます。

まとめ

今回はJavaのprotectedアクセス修飾子について、継承との関係を中心に掘り下げてきました。

  • protectedは、「自分自身」と「自分を継承したクラス(子クラス)」、そして「同じパッケージ内のクラス」からアクセスが可能です。
  • それ以外の「別のパッケージの関係ないクラス」からはアクセスできません。
  • 主に、子クラスが親クラスの特定のメンバにアクセスする必要があるが、外部には公開したくない場合に使われます。

publicが「誰にでもオープン」、privateが「自分だけの秘密」だとすると、protectedは「家族(継承関係)や親しい仲間(同じパッケージ)には見せるけど、他には見せない」といったイメージですね。

これでJavaの主要なアクセス修飾子であるpublicprivateprotectedの3つを学びました。これらの修飾子を適切に使いこなすことで、より安全で保守性の高いプログラムを書くことができるようになります。

これからもJavaの学習を一緒に頑張っていきましょう!

コメント

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