私の歴史と今

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

Javaのインスタンス変数には多態性(多相性)がない罠

Javaには多態性(多相性、ポリモーフィズム)という便利な機能があるが、多態性があるのはインスタンスメソッドのみで、インスタンス変数には多態性はない。
実例として、下記3つのクラスを考えて見ます。Superクラスを親クラスとして、Sub1,Sub2クラスがその子クラスとなっています。

class Super{
	public String var = "super";
	public static String staticVar = "staticSuper";

	public void showVar(){
		System.out.println("インスタンスメソッド=["+var+"]");
	}

	public static void showStaticVar(){
		System.out.println("クラスメソッド=["+staticVar+"]");
	}
}

class Sub1 extends Super{  // オーバーライドなし
}

class Sub2 extends Super{  // すべてオーバーライド
	public String var = "sub";
	public static String staticVar = "staticSub";

	public void showVar(){
		System.out.println("インスタンスメソッド=["+var+"]");
	}

	public static void showStaticVar(){
		System.out.println("クラスメソッド=["+staticVar+"]");
	}
}

これらのクラスに対して、下記のコードを実行した場合は何の問題もありません。

public class Test {
	public static void main(String[] args) {
		System.out.println("●Super型変数にSuper型オブジェクト");
		{
			Super sup = new Super();
			System.out.println("インスタンス変数=["+sup.var+"]");
			sup.showVar();
			System.out.println("クラス変数=["+sup.var+"]");
			sup.showStaticVar();
		}
		System.out.println("●Sub1型変数にSub1型オブジェクト");
		{
			Sub1 sub1 = new Sub1();
			System.out.println("インスタンス変数=["+sub1.var+"]");
			sub1.showVar();
			System.out.println("クラス変数=["+sub1.var+"]");
			sub1.showStaticVar();
		}
		System.out.println("●Sub2型変数にSub2型オブジェクト");
		{
			Sub2 sub2 = new Sub2();
			System.out.println("インスタンス変数=["+sub2.var+"]");
			sub2.showVar();
			System.out.println("クラス変数=["+sub2.var+"]");
			sub2.showStaticVar();
		}
	}
}

実行結果は次のようになります。

●Super型変数にSuper型オブジェクト・・・(a)
インスタンス変数=[super]
インスタンスメソッド=[super]
クラス変数=[super]
クラスメソッド=[staticSuper]
●Sub1型変数にSub1型オブジェクト・・・・(b)
インスタンス変数=[super]
インスタンスメソッド=[super]
クラス変数=[super]
クラスメソッド=[staticSuper]
●Sub2型変数にSub2型オブジェクト・・・・(c)
インスタンス変数=[sub]
インスタンスメソッド=[sub]
クラス変数=[sub]
クラスメソッド=[staticSub]

この結果は至って当然です。変数の型と変数が参照しているオブジェクトの型が同一なので、参照オブジェクトの変数/メソッドが呼び出されているだけです。ただしSub1クラスはSuperクラスを継承してるだけの空クラスなので、(a)と(b)は同じ結果になっています。
では次に、Super型変数にSub1型オブジェクトを参照させた場合はどうなるでしょうか。

		System.out.println("●Super型変数にSub1型オブジェクト");
		{
			Super sup = new Sub1();
			System.out.println("インスタンス変数=["+sup.var+"]");
			sup.showVar();
			System.out.println("クラス変数=["+sup.var+"]");
			sup.showStaticVar();
		}

この実行結果は次のようになりますが、Sub1クラスでは何もオーバーライドしていないので(a)(b)と同一結果になるのは当然ですね。

●Super型変数にSub1型オブジェクト・・・(d)
インスタンス変数=[super]
インスタンスメソッド=[super]
クラス変数=[super]
クラスメソッド=[staticSuper]

では問題の「Super型変数にSub1型オブジェクトを参照させた場合」を考えて見ます。コードは以下です。

		System.out.println("●Super型変数にSub2型オブジェクト");
		{
			Super sup = new Sub2();
			System.out.println("インスタンス変数=["+sup.var+"]"); // (1)
			sup.showVar();                                        // (2)
			System.out.println("クラス変数=["+sup.var+"]");       // (3)
			sup.showStaticVar();                                  // (4)
		}

次のような実行結果になります。

●Super型変数にSub2型オブジェクト・・・(e)
インスタンス変数=[super]
インスタンスメソッド=[sub]
クラス変数=[super]
クラスメソッド=[staticSuper]

コード(2)の実行結果は、予想していたとおりだと思いますが、問題は(1)です。変数supはSub2型オブジェクトを参照している訳ですが、sup.varは、そのオブジェクトのインスタンス変数ではなく、親クラスのオブジェクトのインスタンス変数を指しています。奇妙ですね。
また同じく、クラス変数/クラスメソッドも親クラスの実行結果となっています。
つまり、多態性は、インスタンスメソッドのみに備わる機能だということになります。Sub2クラスのインスタンスの参照を、Super型変数に格納した場合もSub2型変数に格納した場合も、showVar()メソッドの呼び出しはSub2クラスのインスタンスに対して実行されます。(言い換えれば、Sub2クラスのインスタンスの参照を使って、オーバーライドされた親クラスのメソッドを呼び出すことができないということ)
一方、インスタンス変数の場合は、そのインスタンスの型と同じ型の変数で参照された場合のみアクセスされます。(言い換えれば、Sub2クラスのインスタンスの参照を使って、そのインスタンスインスタンス変数にも、親のインスタンス変数にもアクセスできるということ。そのためには格納する変数の型を変えてやればいい)