キャストなしでスーパークラス型のthis参照を表す式
概要
super参照っぽいものを作ります。
前提
thisは参照を表す式として使えますが、superは参照を表す式として使えません。
// thisは参照 System.out.println(this); // コンパイルエラー。superは参照ではない // System.out.println(super);
隠蔽と式の型
式の型をスーパークラスにすることで、隠蔽されたスーパークラスのフィールドにアクセスすることができます。
たとえば、クラスCと、そのサブクラスSubCがあるとします。
CとSubCは同名のフィールドfieldを持っているとします。
するとSubC内ではthis.fieldはサブクラスのフィールドを表すことになります。 (1)
ところが、Cでthisをキャストすると、式の型がCになるので、スーパークラスのフィールドを表すことになり、アクセスが可能になります。 (2)
キャストを除く
以上をふまえて、super参照っぽいもの、つまり式の型がスーパークラスで参照がthisを表すようなそんな式を作ってみます。
以下がそのコードです。
package test; public class SuperTest { private static class C { String field = "C.field"; public void printThis() { // thisは参照 System.out.println(this); // コンパイルエラー。superは参照ではない // System.out.println(super); } } private static class SubC extends C { // hiding String field = "SubC.field"; public void printField() { // (1) SubC.field System.out.println(this.field); // (2) C.field System.out.println(((C) this).field); // (3) (? extends C).field System.out.println(super.getClass().cast(this).field); // (4) C.field System.out.println((true ? this : super.getClass().cast(this)).field); // (5) C.field System.out.println((true ? this : super.getClass().cast(null)).field); // (6) C.field System.out.println((true ? this : super.getClass().getEnumConstants()[-1]).field); System.out.println(super.field); } } public static void main(final String[] args) { new SubC().printField(); } }
簡単なのは(2)と同様thisをスーパークラスの型 (ここではC) にキャストすることですが、明示的なキャストが必要ではスーパークラスを知る必要があるので、テンプレートなどに使いづらいですね。
そこでまずキャストを動的にします。具体的には、SubC中でsuper.getClass() の型が Class<? extends C>*1になる*2ことを利用すると、super.getClass().cast(this)は、 (? extends C)*3型になります。
これだけでもスーパークラスのフィールドを参照することができるようになります。 (3)
ただ、(? extends C)型はC型でないのでこれではまだ完全ではありません。
そこで、条件式の型が2つの式の共通のスーパータイプになることを利用して、thisとsuper.getClass.cast(this)の共通のスーパータイプを取ればC型を得られます。 (4)
条件式の2つの式うち、片方は型を変換するためだけの式なので、片方はthis参照を表す式でなくてもかまいません。 (5)
さらに、片方は評価されないので、評価すると例外がスローされるような式でも型が合っていれば問題ありません。
そこで、Classオブジェクトの型変数を戻り値型に含むgetEnumConstantsも使えます。*4 (6)
かくして、キャストなしでスーパークラスの型をもつthisを作ることができました。
参考まで実行結果
SubC.field C.field C.field C.field C.field C.field C.field
*1:捕捉変換されます
*2:C.getClass()の型はClass<? extends |C|> http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#getClass%28%29
*3:捕捉変換されます
*4:newInstanceは検査例外をスローするのでおすすめしません