2011年03月10日

Javaのトリビア:javacが自動生成するメソッド(2)

前回の続きです。

知っていたからといってどうということはないのですが、Javaの内部クラスの理解が深まれば幸いです。



内部クラスから外部クラスの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をつけた内部クラスのほうが無駄がないということですね。



トラックバックURL

コメントする

名前
 
  絵文字
 
 
記事検索
最新コメント
アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

QRコード
QRコード