2011年03月10日
Javaのトリビア:javacが自動生成するメソッド(2)
内部クラスから外部クラスのprivateの変数の読み書き
前回は内部クラスから外部クラスのprivateのメソッドの呼び出しについて調べました。同じようなことが内部クラスから外部クラスのprivateの変数の読み書きでも起こります。Java仮想マシンのレベルでは他のクラスからprivateの変数の読み書きはできないので、javacが抜け道のメソッドを自動生成します。
class A2 { InnerA innerA = new InnerA(); private int v; class InnerA { void call_outer() { v = v ^ 1; System.out.println("v="+ v); } } public static void main(String args[]) { A2 a = new A2(); a.innerA.call_outer(); } }
これのバイトコードを見てみます。
$ javap -c -private A2 Compiled from "A2.java" class A2 extends java.lang.Object{ A2$InnerA innerA; private int v; A2(); Code: 0: aload_0 1: invokespecial #2; //Method java/lang/Object."":()V 4: aload_0 5: new #3; //class A2$InnerA 8: dup 9: aload_0 10: invokespecial #4; //Method A2$InnerA." ":(LA2;)V 13: putfield #5; //Field innerA:LA2$InnerA; 16: return public static void main(java.lang.String[]); Code: 0: new #6; //class A2 3: dup 4: invokespecial #7; //Method " ":()V 7: astore_1 8: aload_1 9: getfield #5; //Field innerA:LA2$InnerA; 12: invokevirtual #8; //Method A2$InnerA.call_outer:()V 15: return static int access$002(A2, int); Code: 0: aload_0 1: iload_1 2: dup_x1 3: putfield #1; //Field v:I 6: ireturn static int access$000(A2); Code: 0: aload_0 1: getfield #1; //Field v:I 4: ireturn }
予想どおりgetterとsetterのメソッドができています。
Java言語風に書くとこうなります。
static int access$002(A2 a, int val) { a.v = val; return val; } static int access$000(A2 a) { return a.v; }
setterの方は戻り値はvoidで十分なので、ちょっと冗長ですね。
ここでちょっとソースを変更してみます。
$ diff -u A2.java.org A2.java --- A2.java.org 2011-03-07 19:23:24.766064792 +0900 +++ A2.java 2011-03-07 19:23:38.570627949 +0900 @@ -6,7 +6,7 @@ class InnerA { void call_outer() { - v = v ^ 1; + v ^= 1; System.out.println("v="+ v); } }
すると追加されたメソッドが以下のように変わりました。
$ javap -c -private A2 Compiled from "A2.java" class A2 extends java.lang.Object{ A2$InnerA innerA; ... static int access$080(A2, int); Code: 0: aload_0 1: dup 2: getfield #1; //Field v:I 5: iload_1 6: ixor 7: dup_x1 8: putfield #1; //Field v:I 11: ireturn static int access$000(A2); Code: 0: aload_0 1: getfield #1; //Field v:I 4: ireturn }
^= の演算子がまとめてひとつのメソッドになりました。
Java言語風に書くと
static int access$080(A2 a, int val) { return (a.v = a.v ^ val); }
javacは思ったより工夫していますね。
staticの内部クラス
staticをつけた内部クラスはネストクラスと呼ばれたりします。
class As { private static void m() { new Throwable().printStackTrace(); } static class InnerA { static void call_outer() { m(); } } public static void main(String args[]) { InnerA.call_outer(); } }
staticの有無でどのような違いがでるでしょうか。
この内部クラスのバイトコードを見てみます。
$ javap -c -private 'As$InnerA' Compiled from "As.java" class As$InnerA extends java.lang.Object{ As$InnerA(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return static void call_outer(); Code: 0: invokestatic #2; //Method As.access$000:()V 3: return }
前回みたstaticでない内部クラスではコンストラクタで外側のクラスのインスタンスの参照を保持するようになっていましたが、staticの内部クラスではそのようなものはありません。通常のクラスのコンストラクタと同じです。
つまり、コンストラクタで外側のクラスのインスタンスメソッド、インスタンス変数にアクセスする必要が無い場合にはstaticをつけた内部クラスのほうが無駄がないということですね。