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


トップレベルクラスと内部クラスとはなにか?

以前のバージョンでは、Java はパッケージのメンバを対象にしたトップレベルクラスだけをサポートしました。1.1 バージョンでは、Java 1.1 プログラマは現在文のブロック内でローカルに、または式内で(匿名で)他のクラスのメンバとして、内部クラスを定義することができます。

内部クラスを有用にするいくつかのプロパティを次に示します:

内部クラスは、プログラミング言語 Beta が開拓したクラスベースのプログラミングが付いた、ブロック構造の組み合わせから生まれたものです。内部クラスにブロック構造を使用することで、Java プログラマが簡単にオブジェクトを一緒に接続できるようになります。それは、クラスをプログラマが操作する必要のあるオブジェクトの近くに定義でき、必要な名前を直接使用できるからです。クラスの配置に関する制限を取り除くことで、Java のスコーピング規則は、 Pascal および Scheme などの古典的なブロック構造言語のように、より規則的なものになります。

さらに、プログラマはクラスをトップレベルクラスの static メンバとして定義できます。static クラスメンバであるクラスとパッケージメンバであるクラスは、両方ともトップレベルクラスと呼ばれます。これらと内部クラスの違いは、トップレベルクラスが自分のインスタンス変数だけを直接使用できるという点です。このようにクラスをネストする機能によって、すべてのトップレベルクラスが、パッケージ様の組織を第 2 トップレベルクラスの論理的に関連したグループに提供できるようになります。このクラスすべては完全なアクセスを private メンバに分け与えます。

内部クラスとネストされたトップレベルクラスはコンパイラによって実装され、Java Virtual Machine に対する変更をなにも必要としません。これらは既存の Java プログラムとのソースまたはバイナリ互換を損ないません。

新しいネストされたクラスの構築物はすべて、内部クラスを使用しない Java 1.0 コードへの変換をとおして指定します。Java 1.1 コンパイラが Java virtual machine バイトコードを生成しているとき、異なる Java 1.1 コンパイラが生成するバイナリが互換になるように、これらのバイトコードはこの(仮説の)ソース対ソース変換の結果を示す必要があります。バイトコードはまた、特定の属性と共にタグを付けられ、ネストされたクラスの存在を他の Java 1.1 コンパイラに指摘する必要があります。次でさらにこのことを検討します。

例: 簡単なアダプタクラス

同じ型でない別のオブジェクトの代わりに、指定インタフェース型を使用して、メソッド呼び出しを受けるアダプタクラスの設計について考えます。アダプタクラスは一般的に、 AWT および Java Bean コンポーネントからイベントを受け取るために必要です。Java 1.1 では、アダプタクラスは、アダプタを必要とするクラス内に配置される内部クラスとして、最も簡単に定義できます。

スタックを実装し、上部から下へスタックの要素を列挙する不完全なクラス FixedStack を、次に示します:

    public class FixedStack {
        Object array[];
        int top = 0;
        FixedStack(int fixedSizeLimit) {
            array = new Object[fixedSizeLimit];
        }
    	


        public void push(Object item) {
            array[top++] = item;
        }
        public boolean isEmpty() {
            return top == 0;
        }
        // other stack methods go here...
    
        /** This adapter class is defined as part of its target class,
         *  It is placed alongside the variables it needs to access.
         */
        class Enumerator implements java.util.Enumeration {
            int count = top;
            public boolean hasMoreElements() {
                return count > 0;
            }
            public Object nextElement() {
                if (count == 0)
                    throw new NoSuchElementException("FixedStack");
                return array[--count];
            }
        }
        public java.util.Enumeration elements() {
            return new Enumerator();
        }
    }

インタフェース java.util.Enumeration は一連の値を、クライアントに通信するために使用します。FixedStack は直接 Enumeration インタフェースを実装しない(すべきでない! )ために、別個のアダプタクラスが一連の要素を Enumeration の形式で提出するために必要です。もちろん、アダプタクラスには要素のスタック配列へのある種のアクセスが必要です。プログラマがアダプタクラスの定義を FixedStack の内部に置く場合、アダプタのコードはスタックオブジェクトのインスタンス変数を直接参照することができます。

Java では、クラスの非 static メンバはお互いに参照することができ、これらはすべてカレントインスタンス this に関連する意味を持ちます。こうして、インスタンス変数 FixedStackarray は、インスタンスメソッド push および内部クラス FixedStack.Enumerator の本体全体で使用可能になります。インスタンスメソッド本体がカレントインスタンス this を"知っている"ように、Enumerator のようなすべての内部クラス内のコードは、その親インスタンス、つまり array のような変数がフェッチされる親クラスのインスタンスを"知っています"。

FixedStack の例が不完全である点の 1 つは、FixedStack およびその Enumerator の操作の間に競合条件があることです。プッシュとポップの順番が nextElement への呼び出しの間に発生する場合、返される値は以前に列挙した値に正しく関連しない可能性があります; これはスタックの現在の終点を超えた"ごみの値"でさえありえます。このような競合条件にならないように定義すること、またはクラスの使用制限をドキュメント化することはプログラマの責任です。この点は後述します。競合に対する防御には次のようなものがあります:

    public class FixedStack {
        ...
        synchronized public void push(Object item) {
            array[top++] = item;
        }
        class Enumerator implements java.util.Enumeration {
            ...
            public Object nextElement() {
                synchronized (FixedStack.this) {
                    if (count > top)  count = top;
                    if (count == 0)
                        throw new NoSuchElementException("FixedStack");
                    return array[--count];
                }
            }

FixedStack.this は親インスタンスを参照します。

例: ローカルクラス

クラス定義がブロックに対してローカルのとき、これは同じブロック内の普通の式に使用可能なすべての名前にアクセスする可能性があります。次に例を示します:

        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();
        }

しばらくの間、このコードがどのように機能するかについてなにも述べていませんが、Java のスコーピングと変数セマンティックスの規則は正確にこのコードが なにを行うのかを必要とします。メソッド myEnumerate が戻った後でも、array は依然内部オブジェクトが使用できます; これは C でのように"去って"いきません。代わりに、その値は E の 2 つのメソッドを含めて必要なときいつでも使用可能です。

最後の宣言に注意してください。array などの局所 final 変数は 1.1 での新しい機能です。事実、1 つのクラスのローカル変数またはパラメータを別の(内部)クラスが参照する場合、これを final と宣言する必要があります。潜在的な同期の問題があるため、2 つのオブジェクトが変更可能なローカル変数へのアクセスを共有する方法は、設計上ありません。状態変数 count は、1 要素配列に変更されなかったならば、局所変数としてコードできなかったでしょう:

        Enumeration myEnumerate(final Object array[]) {
            final int count[] = {0}; // final reference to mutable array
            class E implements Enumeration {
                public boolean hasMoreElements()
                    { return count[0] < array.length; }   ...

(継承と辞書スコーピングの組み合わせは、混乱を招くことがあります。例えば、クラス Earray という名前のフィールドを Enumeration から継承すれば、そのフィールドは親スコープの同じ名前のパラメータを覆い隠すでしょう。このようなケースで曖昧さを防ぐために、Java 1.1 では、継承された名前が親ブロックまたはクラススコープで定義されたものを覆い隠せるようにします。しかし、明示的な修飾なしでこの名前を使用することは禁じられます。)

匿名クラス

以前の例では、ローカルクラス名 E はコードに対して明瞭さをほとんど加えてはいません。問題はこれが短すぎることではありません: 長い名前でも維持者にわずかしか、クラス本体内で一目で見られるもの以上に追加情報を伝達しないでしょう。非常に小さいアダプタクラスをできる限り簡潔にするために、Java 1.1 ではローカルオブジェクトの省略表記を許しています。単一式の構文は、 匿名 クラスの定義をインスタンスの割り当てと組み合わせます:

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

一般的に、式 new (インスタンス生成式)はクラス本体で終了できます。 この効果は、クラス(またはインタフェース)を new トークンの名をとって命名し、与えられた本体と共にこれをサブクラス化(または実装)することです。結果の匿名内部クラスは、プログラマがこれをローカルに名前を付け、文のカレントブロックで定義した場合と同じ意味を持っています。

このような匿名構築物は、深くネストされたコードを避けるために単純にしておく必要があります。正しく使用するとき、これらはローカルクラスまたはトップレベルアダプタクラスと名付けられた代替より理解し易くかつ維持し易いものです。

匿名クラスが 1 行か 2 行の実行可能コードを含む場合、その意味はおそらく自明のものではなく、説明用のローカル名をクラスまたは(ローカル変数経由で)インスタンスのどちらかに与える必要があります。

匿名クラスは初期化子を持つことはできますが、構築子を持つことはできません。関連する式 new (しばしば空)の引数リストは、暗示的にスーパークラスの構築子に渡されます。

既にヒントを与えたように、匿名クラスがインタフェース I から引き出された場合、実際のスーパークラスは Object であり、このクラスはそれを拡張するより I を実装します。(明示的 implements 節は不当です。) これは、インタフェース名が正当にキーワード new に従うことができる唯一の方法です。このようなケースでは、引数リストは実際のスーパークラス、Object の構築子に適合するように、常に null でなければなりません。


目次 | 前項目 | 次項目

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