| 目次 | 前項目 | 次項目 | 内部クラス仕様 |
さらに、プログラマはクラスをトップレベルクラスの static メンバとして定義できます。static クラスメンバであるクラスとパッケージメンバであるクラスは、両方ともトップレベルクラスと呼ばれます。これらと内部クラスの違いは、トップレベルクラスが自分のインスタンス変数だけを直接使用できるという点です。このようにクラスをネストする機能によって、すべてのトップレベルクラスが、パッケージ様の組織を第 2 トップレベルクラスの論理的に関連したグループに提供できるようになります。このクラスすべては完全なアクセスを private メンバに分け与えます。
内部クラスとネストされたトップレベルクラスはコンパイラによって実装され、Java Virtual Machine に対する変更をなにも必要としません。これらは既存の Java プログラムとのソースまたはバイナリ互換を損ないません。
新しいネストされたクラスの構築物はすべて、内部クラスを使用しない Java 1.0 コードへの変換をとおして指定します。Java 1.1 コンパイラが Java virtual machine バイトコードを生成しているとき、異なる Java 1.1 コンパイラが生成するバイナリが互換になるように、これらのバイトコードはこの(仮説の)ソース対ソース変換の結果を示す必要があります。バイトコードはまた、特定の属性と共にタグを付けられ、ネストされたクラスの存在を他の Java 1.1 コンパイラに指摘する必要があります。次でさらにこのことを検討します。
スタックを実装し、上部から下へスタックの要素を列挙する不完全なクラス FixedStack を、次に示します:
インタフェース java.util.Enumeration は一連の値を、クライアントに通信するために使用します。FixedStack は直接 Enumeration インタフェースを実装しない(すべきでない! )ために、別個のアダプタクラスが一連の要素を Enumeration の形式で提出するために必要です。もちろん、アダプタクラスには要素のスタック配列へのある種のアクセスが必要です。プログラマがアダプタクラスの定義を FixedStack の内部に置く場合、アダプタのコードはスタックオブジェクトのインスタンス変数を直接参照することができます。
Java では、クラスの非 static メンバはお互いに参照することができ、これらはすべてカレントインスタンス this に関連する意味を持ちます。こうして、インスタンス変数 FixedStack の array は、インスタンスメソッド push および内部クラス FixedStack.Enumerator の本体全体で使用可能になります。インスタンスメソッド本体がカレントインスタンス this を"知っている"ように、Enumerator のようなすべての内部クラス内のコードは、その親インスタンス、つまり array のような変数がフェッチされる親クラスのインスタンスを"知っています"。
FixedStack の例が不完全である点の 1 つは、FixedStack およびその Enumerator の操作の間に競合条件があることです。プッシュとポップの順番が nextElement への呼び出しの間に発生する場合、返される値は以前に列挙した値に正しく関連しない可能性があります; これはスタックの現在の終点を超えた"ごみの値"でさえありえます。このような競合条件にならないように定義すること、またはクラスの使用制限をドキュメント化することはプログラマの責任です。この点は後述します。競合に対する防御には次のようなものがあります:
式 FixedStack.this は親インスタンスを参照します。
しばらくの間、このコードがどのように機能するかについてなにも述べていませんが、Java のスコーピングと変数セマンティックスの規則は正確にこのコードが なにを行うのかを必要とします。メソッド myEnumerate が戻った後でも、array は依然内部オブジェクトが使用できます; これは C でのように"去って"いきません。代わりに、その値は E の 2 つのメソッドを含めて必要なときいつでも使用可能です。
最後の宣言に注意してください。array などの局所 final 変数は 1.1 での新しい機能です。事実、1 つのクラスのローカル変数またはパラメータを別の(内部)クラスが参照する場合、これを final と宣言する必要があります。潜在的な同期の問題があるため、2 つのオブジェクトが変更可能なローカル変数へのアクセスを共有する方法は、設計上ありません。状態変数 count は、1 要素配列に変更されなかったならば、局所変数としてコードできなかったでしょう:
(継承と辞書スコーピングの組み合わせは、混乱を招くことがあります。例えば、クラス E が array という名前のフィールドを Enumeration から継承すれば、そのフィールドは親スコープの同じ名前のパラメータを覆い隠すでしょう。このようなケースで曖昧さを防ぐために、Java 1.1 では、継承された名前が親ブロックまたはクラススコープで定義されたものを覆い隠せるようにします。しかし、明示的な修飾なしでこの名前を使用することは禁じられます。)
E はコードに対して明瞭さをほとんど加えてはいません。問題はこれが短すぎることではありません: 長い名前でも維持者にわずかしか、クラス本体内で一目で見られるもの以上に追加情報を伝達しないでしょう。非常に小さいアダプタクラスをできる限り簡潔にするために、Java 1.1 ではローカルオブジェクトの省略表記を許しています。単一式の構文は、 匿名 クラスの定義をインスタンスの割り当てと組み合わせます:
一般的に、式 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 宛てに送ってください。