日々常々

ふつうのプログラマがあたりまえにしたいこと。

変数を使用する無名クラスのコンストラクタさん

過去記事を見ながら追記みたいな感じで。

内部クラス/無名内部クラスはエンクロージングインスタンスの型を要求するコンストラクタが生成されて、内部クラスのフィールドにエンクロージングインスタンスが保持される。文字で書くとなんかややこしいけど、コードで書くと単純だ。

class Hoge {
  class Fuga {}
}

これを javac/javap したらこうなる。

Compiled from "Hoge.java"
class Hoge$Fuga {
  final Hoge this$0;
  Hoge$Fuga(Hoge);
}

ここまで復習。ここから追記。

メソッド内の内部クラスに変数を引き渡そうとするとfinalにしなきゃいけないのは周知の通り。 そいつがどう渡されてるかってーと、やはりコンストラクタになる。

public class Hoge {

  public static void main(String... args) throws Exception {
    new Hoge().method();
  }

  void method() throws Exception {
    final Object obj = new Object();
    class Foo { Foo() { } }
    class Bar { Bar() { obj.toString(); } }

    Foo.class.getDeclaredConstructors()[0].newInstance(this);
    Bar.class.getDeclaredConstructors()[0].newInstance(this, obj);
  }
}

同じく javap で。

Compiled from "Hoge.java"
class Hoge$1Foo {
  final Hoge this$0;
  Hoge$1Foo(Hoge);
}

Compiled from "Hoge.java"
class Hoge$1Bar {
  final java.lang.Object val$obj;
  final Hoge this$0;
  Hoge$1Bar();
}

……あれ、Barのコンストラクタが引数無しに見える。まぁいいか。(javap -s とかすると descriptor: (LHoge;Ljava/lang/Object;)V は見える。)

変数を使用しないFooクラスのコンストラクタの引数はエンクロージングインスタンス型1つだけど、変数を使用するBarクラスのコンストラクタはエンクロージングインスタンス型と変数型の2引数になっている。 そんなわけで変数はコンストラクタを通じて内部クラスの変数に保持される。

コードとか諸々見れば伝わるだろけど、説明は難しいね。