2012-03-22

Java: スーパークラスのフィールドの書き換え

スーパークラスのフィールドをサブクラスでオーバーライドしようとしてもフィールドはオーバーライドではなく隠蔽という形になるので、サブクラスをスーパークラスのインスタンスとして扱うときにはオーバーライドしたつもりのフィールドの値がスーパークラスのフィールドの値になってしまいます(例のためfieldをpublicにしています)。

public class A {
	public String field = "A";
}
public class SubA extends A {
	public String field = "SubA";
}
public class Main {
	public static void main(String[] args) {
		SubA subA = new SubA();
		A a = subA;

		System.out.println(subA.field); // SubA
		System.out.println(a.field); // A ← SubAになってほしい
	}
}

スーパークラスとして扱っていてもサブクラスのフィールドの値を参照させたかったので、どうすればできるか試しました。

1. スーパークラスのメソッドを経由する → ×

スーパークラスにメソッドを定義してメソッド経由でフィールドにアクセスさせてもスーパークラスのメソッドがアクセスするのはスーパークラスのフィールドなので、そのメソッドを継承してきたサブクラスでもメソッド内ではスーパークラスのフィールドにアクセスしてしまいます。

public class A {
	public String field = "A";
	public String getField() {
		return field;
	}
}
public class SubA extends A {
	public String field = "SubA";
}
public class Main {
	public static void main(String[] args) {
		SubA subA = new SubA();
		A a = subA;

		System.out.println(subA.getField()); // A ← SubAになってほしい
		System.out.println(a.getField()); // A ← SubAになってほしい
	}
}

2. サブクラスで同名のフィールドを定義せず、コンストラクタ等でフィールドの値を書き換える → ○

これだとフィールドの隠蔽がされないのでサブクラスでは常にスーパークラスのfieldにアクセスするようになります。

public class A {
	public String field = "A";
	public String getField() {
		return field;
	}
}
public class SubA extends A {
	public SubA() {
		field = "SubA";
	}
}
public class Main {
	public static void main(String[] args) {
		SubA subA = new SubA();
		A a = subA;

		System.out.println(subA.getField()); // SubA
		System.out.println(a.getField()); // SubA
	}
}

サブクラスで同名フィールドを定義してしまうと×です。

3. コンストラクタ等で明示的にスーパークラスのフィールドの値を書き換える → ○

サブクラスで同名フィールドを定義していても、superキーワード経由でフィールドにアクセスすればスーパークラスのフィールドを書き換えられます。

public class A {
	public String field = "A";
	public String getField() {
		return field;
	}
}
public class SubA extends A {
	public String field = "SubA";
	public SubA() {
		super.field = this.field;
	}
}
public class Main {
	public static void main(String[] args) {
		SubA subA = new SubA();
		A a = subA;

		System.out.println(subA.getField()); // SubA
		System.out.println(a.getField()); // SubA
	}
}

4. オーバーライドしたメソッドを経由する → ○

サブクラスでスーパークラスと同じメソッドを定義してオーバーライドします。オーバーライドしたサブクラスのメソッドはサブクラスのフィールドにアクセスしてくれます(スーパークラスのフィールドを書き換えたわけではないですが)。ただスーパークラスとサブクラスで同じメソッドを書いているので無駄な感じがします…。

public class A {
	public String field = "A";
	public String getField() {
		return field;
	}
}
public class SubA extends A {
	public String field = "SubA";
	@Override
	public String getField() {
		return field;
	}
}
public class Main {
	public static void main(String[] args) {
		SubA subA = new SubA();
		A a = subA;

		System.out.println(subA.getField()); // SubA
		System.out.println(a.getField()); // SubA
	}
}

静的フィールドの場合

静的フィールドもサブクラスで同名フィールドを定義するとオーバーライドではなく隠蔽になります。またインスタンスメソッドと違い静的メソッドも隠蔽になるので上記4の方法は使えません。2だと問題ありません。3の場合…

public class A {
	public static String FIELD = "A";
	public static String getField() {
		return FIELD;
	}
}
public class SubA extends A {
	public static String FIELD = "SubA";
	static {
		A.FIELD = FIELD;
	}
}
public class Main {
	public static void main(String[] args) {
		System.out.println(SubA.getField()); // SubA
		System.out.println(A.getField()); // SubA
	}
}

static初期化子等の中でsuperが使えないのでクラス名を直に書かないといけません。

ただスーパークラスの静的フィールドを書き換えてしまうと影響が大きいのでやめたほうがいいです…。