6.5 匿名クラス
6.5.1 最も一時的なクラス
ローカル・クラスは再利用を考慮しない、ブロック内で用いる一時的なクラスでした。しかし、それでも同一ブロック内であれば再利用が可能です(そのようなケースは稀ですが)。
Java 言語では、ローカル・クラスよりもさらに局所的な用途に限られるインスタンスを作成する機能があります。それは匿名クラスと呼ばれ、その名の通り、型名を持ちません。匿名クラスは、クラス・インスタンス生成式と同時にクラスを宣言するという驚異的な構文を用います。匿名クラスを宣言するクラス・インスタンス生成式は次のように記述します。
new 型名 (引数リスト) { クラス本体 }
インスタンス . new 型名 (引数リスト) { クラス本体 }
前者は通常のクラスを継承するインスタンスを、後者はメンバ・クラスなどを継承するインスタンスを生成する時に利用します。クラスを継承したくない場合は、Object クラスを継承させます。クラス本体が記述されたクラス・インスタンス生成式は匿名クラスのインスタンスを生成して返します。見てわかるように匿名クラスは識別子がなく、メンバしか保有していません。匿名クラスを継承したり、匿名クラスを再利用するようなことはできないのです。もちろん、そうした性質から匿名クラスを abstract にすることはできません。
匿名クラスを宣言するクラス・インスタンス生成式では、new に続いて指定する型名にインタフェースを指定することも可能です。インタフェースを指定した場合は、そのインタフェースを実装する匿名クラスであることを表します。
匿名クラスを宣言する場所は、クラス・インスタンス生成式を記述できる場所に制限されます。そのため、必ず何らかのクラスに囲まれることを表すと同時に、性質上 static を指定することはできないため、匿名クラスは必然と内部クラスに分類されます。
class Test { public static void main(String args[]) { System.out.println(new Object() { public String toString() { return "Kitty on your lap"; } }); } }
>java Test Kitty on your lap
コード1では、println() メソッドの引数に指定したクラス・インスタンス生成式で、同時に匿名クラスを宣言しています。この匿名クラスのインスタンスは Object クラスを継承し、toString() メソッドをオーバーライドしています。その結果、表示されたのはオーバーライドした toString() メソッドが返した文字列であることが確認できます。
toString() メソッドをオーバーライドしたクラスのインスタンスを一度だけ使いたい場合、これを独立したクラスとして宣言するのは冗長です。そこで、匿名クラスを用いてクラス・インスタンス生成式と同時に専用クラスを宣言してインスタンスを作成しています。ソースファイルをコンパイルすると、ディレクトリには Test$1.class という名前のクラスファイルが生成されます。これが、匿名クラスの実体となります。
匿名クラスは、下手に利用するとプログラムの再利用性を低下させたり、無意味に複雑化させる要因となってしまいます。しかし、インタフェースや抽象クラスを使ってシステムを制御するクラスなどに局所的なインスタンスをインストールする場合、匿名クラスを使ってインタフェースや抽象クラスを実装します。実践では、特にグラフィカル・システムにおけるイベントハンドラ(ボタンを押すなどのイベント発生時に呼び出されるメソッド)やコールバック・メソッド(条件によって他のライブラリやシステムから呼び出されることが目的のメソッド)を提供する手段として利用されています。
interface IShow { void show(); } class Test { String name = "Kitty on your lap"; public static void main(String args[]) { Test.createShow().show(); new Test().getShow().show(); } public static IShow createShow() { return new IShow() { public void show() { System.out.println(toString() + " : Test . createShow() . Local"); } }; } public IShow getShow() { return new IShow() { public void show() { System.out.println(toString() + " : " + name); } }; } }
>java Test Test$1@5224ee : Test . createShow() . Local Test$2@5ff48b : Kitty on your lap
コード2は、6.4 ローカル・クラス コード2を改良したプログラムです。6.4 ローカル・クラス コード2 では、ローカル・クラスとして宣言して実装を隠蔽する手法を使い、インタフェース型を返すメソッドを作成していました。しかし、ローカル・クラスのインスタンスは一度しか作成されていません。これなら、名前を持たせる必要がないため匿名クラスとして宣言した方がスマートだと考えられます。
6.5.2 匿名コンストラクタ
匿名クラスは、名前を持たないという性質上、明示的なコンストラクタを保有することはできません。しかし、匿名クラスの直接のスーパークラスが明示的なコンストラクタとしてパラメータを受ける場合、何らかの方法で引数を渡す必要があります。
匿名クラスのクラス・インスタンス生成式では、コンパイラが暗黙的に匿名クラスのコンストラクタを生成します。これを匿名コンストラクタと呼びます。匿名コンストラクタは暗黙的に、クラス・インスタンス生成式で渡された引数を受け取り、スーパークラスのコンストラクタに、引数が与えられた順番で渡します。匿名コンストラクタはコンパイラが自動的に生成するので、プログラマが意識する必要はありません。
class Point { public final int x ,y; public Point(int x , int y) { this.x = x; this.y = y; } } class Test { public static void main(String args[]) { System.out.println(new Point(400 , 300) { public String toString() { return "{" + x + " , " + y + "}"; } }); } }
>java Test {400 , 300}
コード3では main() メソッドで Point クラスをスーパークラスとする匿名クラスが宣言されています。この匿名クラスのクラス・インスタンス生成式では Point クラスを初期化するための引数が与えられています。そのため、コンパイラはこれらの引数を受け取る匿名コンストラクタを自動的に生成し、スーパークラスである Point クラスに与えられた値を渡します。実行結果を見ると、Point クラスが正しく初期化されていることがわかります。