2011年03月08日
Javaのトリビア: javacが自動生成するメソッド
先日の記事のスタックトレースをよく見ると、android.app.ActivityThread.access$2300 という不思議な名前のメソッドが呼ばれています。ソースコードをさがしてもこのメソッドは見当たりません。実はこれはjavacが自動生成したメソッドなのです。
I/Hello ( 829): on Start I/Hello ( 829): java.lang.Throwable I/Hello ( 829): at com.example.helloandroid.HelloAndroid.onStart(HelloAndroid.java:22) I/Hello ( 829): at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1129) I/Hello ( 829): at android.app.Activity.performStart(Activity.java:3781) I/Hello ( 829): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2636) I/Hello ( 829): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2679) I/Hello ( 829): at android.app.ActivityThread.access$2300(ActivityThread.java:125) I/Hello ( 829): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2033) I/Hello ( 829): at android.os.Handler.dispatchMessage(Handler.java:99) I/Hello ( 829): at android.os.Looper.loop(Looper.java:123) I/Hello ( 829): at android.app.ActivityThread.main(ActivityThread.java:4627) I/Hello ( 829): at java.lang.reflect.Method.invokeNative(Native Method) I/Hello ( 829): at java.lang.reflect.Method.invoke(Method.java:521) I/Hello ( 829): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868) I/Hello ( 829): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626) I/Hello ( 829): at dalvik.system.NativeStart.main(Native Method)
ソースコードを調べると、ActivityThreadの中のHという内部クラスから、その外側のActivityThreadのクラスをhandleLaunchActivityというメソッドを呼び出しています。ポイントはこのhandleLaunchActivityがprivateのメソッドであることです。
Java言語の仕様としては内部クラスからその外側のクラスのprivateなメソッドの呼び出しを許していますが、実はこの内部クラスというのは確かJava1.1で追加された仕様であり、最初から存在したものではありません。
Javaの仮想マシンのレベルでは内部クラスという概念はなくて、全てのクラスは平等に扱われます。そのためprivateなメソッドは他のクラスからは呼び出すことができないようになっています。
そこで、javac(Javaコンパイラ)がこのような場合に外側のクラスにこっそりと裏口のメソッドを追加し、内部クラスからはそのメソッドを通して外側のクラスのprivateメソッドを呼ぶようにコンパイルします。つまり、Java言語の内部クラスの仕様追加はJava仮想マシンを変更せずに、javacのがんばりによって実現しているということです。今回の場合のActivityThread.access$2300というメソッドがjavacが追加したものです。
もっと簡単なサンプルプログラムで見てみましょう。
class A {
InnerA innerA = new InnerA();
private void m() {
new Throwable().printStackTrace();
}
class InnerA {
void call_outer() {
m();
}
}
public static void main(String args[]) {
A a = new A();
a.innerA.call_outer();
}
}
これをコンパイルして実行してみます。
$ javac A.java $ java A java.lang.Throwable at A.m(A.java:6) at A.access$000(A.java:1) at A$InnerA.call_outer(A.java:11) at A.main(A.java:18)
Aの内部クラスのメソッドInnerA.call_outerからA.mのメソッド呼び出しの間に例の自動生成されたメソッドがはさまっています。
このメソッドのソースは無いのでバイトコードを見てみましょう。
$ javap -c -private A
Compiled from "A.java"
class A extends java.lang.Object{
A$InnerA innerA;
...
private void m();
Code:
0: new #6; //class java/lang/Throwable
3: dup
4: invokespecial #7; //Method java/lang/Throwable."":()V
7: invokevirtual #8; //Method java/lang/Throwable.printStackTrace:()V
10: return
public static void main(java.lang.String[]);
Code:
0: new #9; //class A
3: dup
4: invokespecial #10; //Method "":()V
7: astore_1
8: aload_1
9: getfield #5; //Field innerA:LA$InnerA;
12: invokevirtual #11; //Method A$InnerA.call_outer:()V
15: return
static void access$000(A);
Code:
0: aload_0
1: invokespecial #1; //Method m:()V
4: return
}
最後のメソッドがそうです。たった3命令のメソッドです。
これをわかりやすくJava風に書くとこうなります。
static void access$000(A a) {
a.m();
}
もし、このように一段階余計なメソッドを経由するのが嫌ならば、Aクラスのm()をprivateにするのをやめれば、直接呼び出すようになります。
$ diff -u A.java.org A.java
--- A.java.org 2011-03-07 16:54:26.366454861 +0900
+++ A.java 2011-03-07 16:54:38.532946229 +0900
@@ -2,7 +2,7 @@
InnerA innerA = new InnerA();
- private void m() {
+ /*private*/ void m() {
new Throwable().printStackTrace();
}
このように修正して、実行してみます。
$ javac A.java $ java A java.lang.Throwable at A.m(A.java:6) at A$InnerA.call_outer(A.java:11) at A.main(A.java:18)
InnerA.call_outerからA.mを直接呼ぶようになりました。
このことはJavaVMやバイトコードを扱うツールを作るとき以外は気にする必要がないのですが、最適化をしようとしてプロファイルをとったときに、もしこのようなメソッドが上位に来ている場合には役に立つ知識かもしれません。
ついでに内部クラスのバイトコードも見てみましょう。(ファイル名の中の$がshellに解釈されないように\をつけるかシングルクオーツで囲む必要があります。)
$ javap -c -private 'A$InnerA'
Compiled from "A.java"
class A$InnerA extends java.lang.Object{
final A this$0;
A$InnerA(A);
Code:
0: aload_0
1: aload_1
2: putfield #1; //Field this$0:LA;
5: aload_0
6: invokespecial #2; //Method java/lang/Object."":()V
9: return
void call_outer();
Code:
0: aload_0
1: getfield #1; //Field this$0:LA;
4: invokevirtual #3; //Method A.m:()V
7: return
}
これをJava言語風に戻してみると
class A$InnerA {
final A this$0; // 外側のクラスのインスタンス
A$InnerA(A a) {
this$0 = a;
}
void call_outer() {
this$0.m();
}
}
この内部クラスはjavacが自動生成したインスタンス変数this$0で外側のクラスのインスタンスへの参照を保持していることがわかります。そして、それはコンストラクタで初期化されています。
今回は内部クラスから外部クラスのprivateのメソッドを呼び出すときのことを調べました。似たようなことがprivateの変数でも起こります。それは次回で。