私の歴史と今

読んでて恥ずかしくなるのが私の歴史。だけどそのときは今現在のように真面目に書いていた訳でね。そんな私の今を書いていく。

Javaでダウンキャストするとコンパイルエラーになる子クラスがある罠

アクセス修飾子に関すること。親クラス方の参照変数が子クラスのインスタンスを参照している場合、そのメンバへのアクセス可否は、親クラスと子クラスのどちらのアクセス修飾子が判断基準になるか?
つまり、下記コードを見て下さい。

Sup sup = new Sub();
sup.meth();

Supは親クラス、Subは子クラスです。親クラス型の参照変数で子クラスのインスタンスを参照しており、meth()メソッドを呼び出しています。この時、この呼び出しが成功する(コンパイルが成功する)条件は、下記いずれになるでしょうか?

    1. 親クラスのmeth()メソッドを参照できること(親クラスのmethメソッド可視
    2. 子クラスのmeth()メソッドを参照できること(子クラスのmethメソッド可視
    3. 上記2つの両方

もちろん、子クラスでmeth()メソッドはオーバーライドされており、実行されるのは子クラスのmeth()メソッドです。

検証

パッケージ・クラス構成
親クラスと子クラスは別パッケージになっており、テストクラスは親クラスと同一パッケージになっています。

extend
│
├─sub
│      Sub.java     (子クラス)
│
└─sup
        Sup.java     (親クラス)
        Tester.java  (テストクラス ※mainメソッドあり)

親クラス
オーバーライドされるメソッドがprotectedになっている。

package extend.sup;

public class Sup {

	public String str = "sup";

	// protected にしている
	protected void showStr(){
		System.out.println("Sup.str=["+str+"]");
	}
}

子クラス
親クラスとは別パッケージになっており、showStrメソッドをオーバーライドしている(親クラスのshowStrメソッドはprotectedなのでオーバーライドできる)。

package extend.sub;

import extend.sup.Sup;

public class Sub extends Sup{

	protected void showStr(){
		System.out.println("Sub.str=["+str+"]");
	}
}

テストクラス

package extend.sup;

import extend.sub.Sub;

public class Tester {

	public static void main(String[] args){
		Sub sub = new Sub();
		sub.showStr(); // 検証(1)

		Sup sup = sub;
		sup.showStr(); // 検証(2)
	}
}

「検証(1)」,「検証(2)」と書いてある行が検証ポイントです。
前提として、Supクラスはpublicクラスであり、そのshowStrメソッドはprotectedメンバーであるため、Supクラスと同一パッケージにあるTesterクラスからshowStrメソッドにアクセスすることは可能です。
それに対して、Subクラスは、アクセス修飾子的にはSupクラスと同一ですが、Subクラスと別パッケージにあるTesterクラスからshowStrメソッドにアクセスすることは不可能です。そこで検証です。
検証(1)
Sub型変数がSubクラスのインスタンスを参照しており、そのshowStrメソッドを呼び出しています。アクセス修飾子の標準的な考え方で結論がでます。SubクラスのshowStrメソッドは、他パッケージからアクセス不可なので、当然コンパイルエラーとなります。
検証(2)
Sup型変数がSubクラスのインスタンスを参照しており、そのshowStrメソッドを呼び出しています。検証(1)との違いは変数型です。TesterクラスからSubクラスのshowStrメソッドを呼び出していると考えれば、検証(1)と同様にコンパイルエラーとなるべきです。しかし、コンパイルは成功します。ということは、呼び出し可否は、親クラスのアクセス修飾子を基準にしているといえます。

そこで、ダウンキャストについて面白いことがおきます。

Sup sup = new Sub();
sup.showStr();

Sub sub = (Sub)sup;
sub.showStr(); // (3) コンパイルエラー

Sup型変数でshowStrメソッドを呼び出せるのだから、Sub型変数でも呼び出せるだろうと考えてのコードです。いずれの参照型でも同じメソッドが呼び出されるのですから、そのように考えるのも当然です。しかし、このコードは既に検証したとおり、(3)の行でコンパイルエラーとなります。

    1. 「可視/不可視は、参照型のアクセス修飾子によって決まるのであって、実際に参照しているインスタンスの型とは無関係である」ということになります。
    2. また、「コンパイル時に決定される」ということも大事なポイントになるかと思います。