内部クラスのコンストラクタ
下記は内部クラスというか匿名クラスを使うプログラムで、匿名クラスの中では自由変数(free-variable)を使っています。このようなプログラムはバイトコードに変換される際に少し変わった動きをします。
class Hoge { void foo() { final int freeVar = 1; Runnable r = new Runnable() { public void run() { final int boundVar = 2; System.out.println(freeVar + boundVar); } } ... } }
匿名クラスは、下のように通常のクラスに変換されたのちにコンパイルされます。その際、自由変数をコンストラクタに引き渡し(enclosing classのスコープのためにthisも持っている)、一度フィールドとして設定してから匿名クラスの中で利用します。ローカルクラスや内部クラスでも似たような感じです。
class Hoge$0 implements Runnable { synthetic private final Hoge $this; synthetic private final int $freeVar; public Hoge$0(Hoge $this, int $freeVar) { super(); this.$this = $this; this.$freeVar = $freeVar; } public void run() { final int boundVar = 2; System.out.println(this.$freeVar + boundVar); } } class Hoge { void foo() { final int freeVar = 1; Runnable r = new $Anonymous(this, freeVar); ... } }
このように、クロージャを利用する際に、自由変数を関数の引数に追加してしまうような変換をlambda-liftingと確か呼んだ記憶があります。
で、ここまでが前置きで、上記Hoge$0のlambda-liftingされた変数をクラスファイルからどう検出しようかなと。ソースコードがあれば一発なんですが、それだと多少面倒だということで。さらに、lambda-liftingする際のルールはコンパイラごとに決めていことになっていて、
匿名クラスの場合は下のようになるはず*1なので比較的解析が楽です。つまり、明示的親コンストラクタ起動super(...)のシグネチャを解析して、匿名クラスで同一のシグネチャを持つコンストラクタがlambda-lifting前のものとなります。
public void Hoge$0(<free-variables>, <original-formal-parameters>) { super(<original-formal-parameters>); // lambda-lifting前 ... }
ローカルクラスの場合はlambda-lifting前のコンストラクタの形状を推測できないため、多少力技で解析する必要がありそうです。現在のjavacの実装では、syntheticフラグが立ったフィールドにlambda-liftingによって追加された引数を退避するので、これを前提にして元のコンストラクタのシグネチャを判別するという手があります。ただ、syntheticフラグはそのようなフィールドに付与されるのが必須であるという仕様ではない*2ので、実装依存になりそうです。
ついでに言うと、これだけのためにクラスファイル解析するのが面倒すぎ。
もうちょい考えてみます。
余談ですが、限定的クラスインスタンス生成式というのもあります。
class Hoge { int hoge; class Foo { void foo() { System.out.println(Hoge.this.hoge); } } void bar() { // 非限定的 Foo thisFoo = new Foo(); // 限定的 Hoge that = new Hoge(); Foo thatFoo = that.new Foo(); this.hoge = 100; that.hoge = 200; thisFoo.foo(); thatFoo.foo(); } }
内部クラスのインスタンス生成式は、this.new ... の省略形だったりします。が、これを使ったプログラムを見たことがない…