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);
}
}こんなふうにするしか手はなさそうです。