内部クラスが言語のバージョン1.1でJavaに追加されたとき、それらは元々1.0互換コードへの変換として定義されていました。この変換の例を見ると、内部クラスが実際にどのように機能するかが非常に明確になると思います。
イアン・ロバーツの答えからのコードを考えてみましょう:
public class Foo {
int val;
public Foo(int v) { val = v; }
class Bar {
public void printVal() {
System.out.println(val);
}
}
public Bar createBar() {
return new Bar();
}
}
1.0互換コードに変換すると、その内部クラスBarは次のようになります。
class Foo$Bar {
private Foo this$0;
Foo$Bar(Foo outerThis) {
this.this$0 = outerThis;
}
public void printVal() {
System.out.println(this$0.val);
}
}
内部クラス名には、一意にするために外部クラス名が前に付けられます。this$0外部のコピーを保持する非表示のプライベートメンバーが追加されますthis。そして、そのメンバーを初期化するために隠しコンストラクタが作成されます。
そして、createBarメソッドを見ると、次のようなものに変換されます。
public Foo$Bar createBar() {
return new Foo$Bar(this);
}
それでは、次のコードを実行するとどうなるか見てみましょう。
Foo f = new Foo(5);
Foo.Bar b = f.createBar();
b.printVal();
最初に、インスタンスをインスタンス化しFoo、valメンバーを5に初期化します(つまりf.val = 5)。
次にを呼び出してf.createBar()、のインスタンスをインスタンスFoo$Bar化し、this$0メンバーをthisfromから渡された値createBar(つまりb.this$0 = f)に初期化します。
最後にwhichはwhich is whichである5 b.printVal()を印刷しようとします。b.this$0.valf.val
これが内部クラスの定期的なインスタンス化です。Bar外部からインスタンス化するとどうなるか見てみましょうFoo。
Foo f = new Foo(5);
Foo.Bar b = f.new Bar();
b.printVal();
1.0変換を再度適用すると、その2行目は次のようになります。
Foo$Bar b = new Foo$Bar(f);
これはf.createBar()呼び出しとほとんど同じです。繰り返しますが、インスタンスをインスタンスFoo$Bar化し、this$0メンバーをfに初期化しています。だから、もう一度、b.this$0 = f。
また、を呼び出すとb.printVal()、5 が印刷さb.thi$0.valれますf.val。
覚えておくべき重要なことは、内部クラスにはthis外部クラスからのコピーを保持する隠しメンバーがあるということです。外部クラス内から内部クラスをインスタンス化すると、現在の値で暗黙的に初期化されますthis。外部クラスの外部から内部クラスをインスタンス化するときは、newキーワードのプレフィックスを介して、使用する外部クラスのインスタンスを明示的に指定します。