Javaのアクセス修飾子protectedで少しハマったので、調べた内容をメモしておきます。
通常、protectedと指定されたフィールドやメソッドは「同一package内と、そのクラスを継承したサブクラス内からアクセスできる」と説明されます。私自身も同様の理解だったのですが、次のようなケースで予想外の挙動となりました。
クラスBaseとChildが別のpackageに属する場合、
package base; public class Base { protected void baseMethod() { System.out.println("Base method."); } }
package child; import base.Base; public class Child extends Base { public void childMethod(Base base) { base.baseMethod(); // NG } }
Childクラスの中でBaseクラスの引数を受け取ってbaseMethod()を呼び出そうとすると、”The method baseMethod() from the type Base is not visible”と怒られてしまうのです。
実は、Javaのprotected指定の意味は、正確には「同一パッケージ内と、そのクラスを継承したサブクラス内から、そのサブクラスへの参照を通してアクセスできる」となります。つまり、Childクラスの中から呼ぶことができるbaseMethod()は、あくまでもChildクラス(またはChildクラスのサブクラス)のbaseMethod()であり、Baseクラスのそれではないのです。
例えば、次のケースではbaseMethod()を呼ぶことができます。
package child; import base.Base; public class Child extends Base { public void childMethod(Child child) { this.baseMethod(); // OK child.baseMethod(); // OK } }
確かに、親クラスのprotectedフィールドをサブクラスから直接書き換えられると問題があるかもしれませんが、そういうフィールドはprivateにすればよい(というか、そうするべき)し、メソッド呼び出しはポリモーフィズムにすることができるので、これは少々余計な制限であるように思います。
どうしてもChildクラスの中からBaseクラスの参照を使ってbaseMethod()を呼び出したい場合は、
package base; public class Base { protected void baseMethod() { System.out.println("Base method."); } protected void invokeBaseMethod(Base base) { base.baseMethod(); } }
package child; import base.Base; public class Child extends Base { public void childMethod(Base base) { invokeBaseMethod(base); } }
こんなふうにするしか手はなさそうです。
java の親クラスへのアクセスはbaseではなくsuperです。
コメントありがとうございます。
ご指摘の通り、自インスタンスの親クラスにはsuper経由でアクセスすることが可能です。(本文の例の場合では、superを明記しなくてもbaseMethod()を呼び出せば自インスタンスの親クラスメソッドを呼び出せます。)
ここで述べているのは、子クラスメソッドが自インスタンス以外の親クラスインスタンスの参照(base)を受け取り、その参照を経由して親クラスのprotectedメソッドを呼び出そうとする(base.baseMethod())というケースです。
このとき、親クラスと子クラスが別のpackageに定義されていると、protectedの「同一パッケージ内」または「そのクラスを継承したサブクラス内から、そのサブクラスへの参照を通してアクセス」という制限をどちらも満たせず、親クラスのprotectedメソッドを呼び出すことができません。
この制限を意識することはあまり無いと思いますが、当時実装していたコードで偶々直面したので、ブログで紹介させてもらいました。
親クラスのオブジェクトの引数名がbaseになっていたのですね。ソースコードを読み間違えていました。
改めて記事を読み直しましたが見当違いな指摘をしていたようです。
失礼しました。