目次 | 前項目 | 次項目 内部クラス仕様


内部クラスはどのように機能するのか?

内部クラスコードは通常それを囲む親クラスのインスタンスに関連して定義されるので、内部クラスインスタンスは親インスタンスを決定できる必要があります。

JavaSoft Java 1.1 コンパイラは、内部クラスを親クラスにリンクする付加的な private インスタンス変数を追加して、これを調整します。この変数は内部クラス構築子に渡されてくる付加的な引数から初期化されます。代わりにこの引数は、内部クラスインスタンスを作成する式によって決定されます; デフォルトでは、作成を行うオブジェクトになります。

Java 1.1 言語仕様では、クラスメンバである型の名前は、Java virtual machine バイトコードを生成する目的で Java 1.0 コードに変換されたとき、内部クラスの完全修飾名から構成されると指定しています。ただし、クラス名に続く各々の`.'文字は`$'で置き換えられるという点を除きます。さらに、各内部クラス構築子は、前に追加された引数の親インスタンスを受け取ります。次に、FixedStack の変換済ソースコードの例を示します:

    public class FixedStack {
        ... (the methods omitted here are unchanged)
        public java.util.Enumeration elements() {
            return new FixedStack$Enumerator(this);
        }
    }
    
    class FixedStack$Enumerator implements java.util.Enumeration {
        private FixedStack this$0; // saved copy of FixedStack.this
        FixedStack$Enumerator(FixedStack this$0) {
            this.this$0 = this$0;
            this.count = this$0.top;
        }
    
        int count;
        public boolean hasMoreElements() {
            return count > 0;
        }
        public Object nextElement() {
            if (count == 0)
                throw new NoSuchElementException("FixedStack");
            return this$0.array[--count];
        }
    }

既に Java または C++ アダプタクラスでプログラムを組んだことのある人は、これに似たコードを書いたことがあるはずです。異なる点は、リンク変数は手作業で定義し、トップレベルアダプタクラスの中で明示的に初期化する必要があることで、Java 1.1 コンパイラはこれらを内部クラス用に自動的に作成します。

Enumerator が、親インスタンスの top または array フィールドを参照する必要のある時、これは this$0 と呼ばれる private リンクを経由して行います。この名前のスペリングは内部クラスの Java 1.0 言語への変換の必須部分で、デバッガおよび同様のツールはこのようなリンクを簡単に認識できます。(ほとんどのプログラマは幸いにもこのような名前に気が付いていません。)

(注: Java 1.1 のいくつかの実装には制限があり、this$0 の初期化は、すべてのスーパークラス構築子が実行されるまで遅延されます。これは、サブクラスメソッドが行う上位レベル参照は、スーパークラス構築子がそのメソッドをたまたま実行した場合に、失敗することを意味します。)

ローカル変数の参照

ブロックに対してローカルなクラス定義は、ローカル変数にアクセスする可能性があります。このことはコンパイラのジョブを複雑にします。ローカルクラスの以前の例を次に示します:

        Enumeration myEnumerate(final Object array[]) {
            class E implements Enumeration {
                int count = 0;
                public boolean hasMoreElements()
                    { return count < array.length; }
                public Object nextElement() {
                    { return array[count++]; }
            }
            return new E();
        }

ローカル変数を内部クラスのメソッドに可視にするために、コンパイラは変数の値を内部クラスがアクセスできる場所にコピーする必要があります。同じ変数を参照する場合、同じ値があらゆる場所で作られ、名前がそのスコープのすべての部分で同じ変数を参照するために一貫して表示される限り、異なる場所で異なるコード順番を使用する可能性があります。

規則によって、array のようなローカル変数は、内部クラスの private フィールド val$array にコピーされます。(arrayfinal であるため、このようなコピーは決して一貫性のない値を含みません。) 各コピー値は、内部クラス構築子に同じ名前の別個の引数として渡されます。

次に結果の変換済コードを示します:

        Enumeration myEnumerate(final Object array[]) {
            return new MyOuterClass$19(array);
        }
    ...
    class MyOuterClass$19 implements Enumeration {
        private Object val$array[];
        int count;
        MyOuterClass$19(Object val$array[])
            { this.val$array = val$array; count = 0; }
        public boolean hasMoreElements()
            { return count < val$array.length; }
        public Object nextElement()
            { return val$array[count++]; }
    }

コンパイラは、変数を内部クラス構築子内だけで使用すると決定できる場合、内部クラスフィールドを変数に割り当てることを避ける可能性があります。

E のようなブロックが定義するクラスは親クラスのメンバではなく、そのブロック外で名前を付けることができないことに注意してください。これはローカル変数に適用されるものと同じスコーピングの制限で、これもまたブロック外で名前を付けられません。事実、ブロック(直接または介在するローカルクラス内部)に含まれるすべてのクラスは、ブロック外で名前を付けられません。このようなクラスすべてはアクセス不可能と呼ばれます。リンクするためには、コンパイラはすべてのアクセス不可能クラスに対して、一意な外部から可視な名前を生成する必要があります。これらの名前の総括的な形式は、クラス名の後に追加の数字または名前を付け、$ 文字で分離します。

また、this$ および val$ で始まるコンパイラが合成した変数名は、ここで説明する使用パターンに従う必要があります。

これらの名前と規則は 1.1 準拠のツールに認識される必要があり、したがってほとんどコンパイル目的のために、従うことを強く求められます。このことについてはバイナリ互換の節でさらに検討します。

奇妙な名前の付いた"this$"と"val$"フィールドおよび余分な構築子引数は、コンパイラによって生成されたバイトコードに追加され、Java ソースコードで直接参照することはできません。同様に、MyOuterClass$19 のようなバイトコードレベルのクラス名をソースコードが使用することはできません(内部クラスについてなにも知らない 1.1 以前のコンパイラを除く)。


目次 | 前項目 | 次項目

内部クラス仕様 (HTML generated by dkramer on March 15, 1997)
Copyright (c) 1996, 1997 Sun Microsystems, Inc. All rights reserved
コメントや訂正は john.rose@eng.sun.com 宛てに送ってください。