あなたの知らない、4つのマニアックなJava文法:【改訂版】Eclipseではじめるプログラミング(17)(3/3 ページ)
これからプログラミングを学習したい方、Javaは難しそうでとっつきづらいという方のためのJavaプログラミング超入門連載です。最新のEclipse 3.4とJava 6を使い大幅に情報量を増やした、連載「Eclipseではじめるプログラミング」の改訂版となります
【3】無名内部クラス(匿名クラス)
内部クラスには、「無名内部クラス」と呼ばれるものもあり、「無名クラス」と呼ばれることもあります。実はJavaでは、クラスに名前を付けずに利用することもできます。その方法で宣言された内部クラスは名前を持たないので、「無名内部クラス」といわれます。RemainViewクラスは、無名内部クラスで実装できます。
Eclipse上でsample17.App4クラスをコピーしてsample17.App7クラスを作成します。sample17.App4クラスのメンバからRemainViewクラスを削除してから、コンストラクタ内で無名内部クラスで登録するようにします。次のように変更します。実行結果はsample17.App4Runクラスの結果と同様なので、省略します。
// 略 view.addObserver(resultView); // 無名内部クラス view.addObserver(new Observer() { @Override public void update() { System.out.println("残り時間:" + count + "[sec]"); } }); // 略
無名内部クラスは制限が多い
このように、無名内部クラスはクラス名を指定せずに実装したいインターフェイスやクラスをnewしながら、そこに実装が必要なコードを記述します。無名内部クラスは、明示的なコンストラクタを持てませんし、これを継承するクラスを作成できません。明示的なextends節やimplements節を持つこともできません。ほかにもありますが、こういったいくつかの制限があります。
明示的なコンストラクタを持てないので、インスタンス生成時の初期化については、それほど自由度はありません。とはいえ、初期化子と初期化ブロックを持つことはできます。
無名内部クラスの継承はextendsを使わない
また、コンストラクタを持つクラスをextendsした無名内部クラスを指定する場合は、スーパークラスのコンストラクタを呼び出しているかのように指定しつつ、オーバーライドしたいメソッドも実装できます。次のようなsample17.App8クラスを作成してみましょう。
package sample17; public class App8 { class Ic implements Ii { String value; Ic(String v) { value = v; } public void print() { System.out.println("Ic1:" + value); } } interface Ii { void print(); } Ii[] objs = new Ii[5]; public void execute() { objs[0] = new Ic("objs[0]"); objs[1] = new Ic("objs[1]") { public void print() { System.out.println("無名内部クラス objs[1]:" + value); } }; objs[2] = new Ii() { public void print() { System.out.println("無名内部クラス objs[2]:"); } }; objs[3] = new Ii() { String value = "objs3"; public void print() { System.out.println("無名内部クラス objs[3]:" + value); } }; objs[4] = new Ii() { String value; { value = "objs" + 4; } public void print() { System.out.println("無名内部クラス objs[4]:" + value); } }; for (Ii o : objs) { o.print(); } } public static void main(String[] args) { App8 app8 = new App8(); app8.execute(); } }
このクラスでは、ネストしたインターフェイスとしてprintメソッドだけを宣言したIiインターフェイスと、それを実装した内部クラスIiを宣言しています。フィールドとしては、Ii型の配列であるobjsだけを持ちます。
objsの最初の要素には、Icクラスをインスタンス化して代入しています。添え字1の要素には、Icクラスをextendsした無名内部クラスのインスタンス化をして代入しています。IcクラスのコンストラクタはString型のパラメータを受け取るので、それを指定しつつ、printメソッドをオーバーライドしています。
添え字2以降の要素へは、Iiインターフェイスをimplementsした無名内部クラスをインスタンス化して代入しています。初期化子や初期化ブロックを使って初期化しています。
最後に、動作確認のために「拡張for文」を使って配列の各要素のprintメソッドを実行しています。以下は、実行結果です。
Ic1:objs[0] 無名内部クラス objs[1]:objs[1] 無名内部クラス objs[2]: 無名内部クラス objs[3]:objs3 無名内部クラス objs[4]:objs4
使いどころ
複雑なデータ構造を内部に持ったり、たくさんのメソッドを持つようなクラスの場合は、無名内部クラスで実装すると可読性が落ちますし、メンテナンスもしにくいので、普通は採用しません。
また、継承によって実装を再利用できませんから、コードを再利用したい場合に採用できません。しかし、RemainViewクラスのように、コンストラクタの必要がなく、処理も単純で、別途再利用する予定もないクラスについては、無名内部クラスで実装したくなります。そういった場合に利用するといいでしょう。
使いどころとしては、ほかにも以下のような例があります。
【4】ローカル内部クラス
無名内部クラスとして実装するには複雑なクラスではあるものの、sample17.App4のコンストラクタ内で1度だけ定義すれば十分なクラスがあったとします。そんな場合は、「ローカル内部クラス」を使います。ローカル内部クラスは、コンストラクタに限らず、メソッド内で宣言できます。
RemainViewクラスをローカル内部クラスを使って実装することもできます。sample17.App7クラスをコピーをしてsample17.App9クラスを作成します。sample17.App9クラスのコンストラクタ内で、次のようにRemainViewクラスを宣言します。
// 略 App9() { count = 12; resultView.setStartValue(count); view.addObserver(progressView); view.addObserver(resultView); class RemainView implements Observer { @Override public void update() { System.out.println("残り時間:" + count + "[sec]"); } } view.addObserver(new RemainView()); } // 略
RemainViewクラスは無名内部クラスで実装しても構わないクラスでしたから、このようにローカル内部クラスで実装する利点が見当たらないので、ピンと来ないかもしれません。ローカル内部クラスとすることで、「どこからどこまでがひとまとまりなのか明確になる」という点だけ理解しておいてください。
使いどころ
GUIアプリケーションなどでは、そこそこ複雑な初期化や処理を持つものの、コンストラクタ内でしか必要のないクラスが出てくる場面がよくあります。
そういったクラスを無名内部クラスで実装すると、どこからどこまでがクラスの宣言なのかよく分からなくなってしまいます。そういったときに、ローカル内部クラスはよく使われます。
LLほどではないが、Javaでも軽量に
前回、今回と、たくさん細かい話が出ましたが、ネストしたクラスについて理解できたでしょうか。初心者のうちは、「自分でネストした型を宣言して使う」機会は、それほど多くはないかもしれませんが、ほかの開発者のコードで見かけることはあるかと思います。そういったときのために、細かい点についても説明をしました。
筆者の考えでは、初心者がネストした型を使う場面は、「同一ファイル内で必要になった時点でクラスを、その場で作成して、いろいろ試してみる」といったライトウェイトプログラミングをするときぐらいです。
Javaでは、RubyやPythonといったLL(Lightweight Language、軽量プログラミング言語)ほど軽量なプログラミングはできませんが、前回と今回で紹介をしたネストした型や内部クラスをうまく利用することで、「思い付いたままに試行錯誤しながらプログラミングをする」といったことが手軽にできるようになります。
試しに作ってみるようなプログラムでは、どんどん使って素早くやりたいことを実現してみましょう。もちろん、通常の開発でもネストした型は有用な場面はありますから、適材適所で利用できるようになりましょう。今回作ったサンプルのソースコードはこちらからダウンロードできます。
筆者紹介
小山博史(こやま ひろし)
情報家電、コンピュータと教育の研究に従事する傍ら、オープンソースソフトウェア、Java技術の普及のための活動を行っている。長野県の地域コミュニティである、SSS(G)やbugs(J)の活動へも参加している。
著書に「基礎Java」(インプレス)、共著に「Javaコレクションフレームワーク」(ソフトバンククリエイティブ)、そのほかに雑誌執筆多数。
- Javaの例外処理で知らないと損する7つのテクニック
- プログラマの宿命! 例外とエラー処理を理解する
- いまさら聞けない「Javadoc」と「アノテーション」入門
- 7ステップで理解するJavaでの列挙型/enum使用法
- 拡張for文の真の実力を知り、反復処理を使いこなせ
- キュー構造をJavaで実装してジェネリック型を理解する
- 強く型付けされているJavaの理解に必修の“型変換”
- あなたの知らない、4つのマニアックなJava文法
- “ネスト”した型で始める軽量Javaプログラミング!?
- Javaは「抽象クラス」で実装を上手に再利用できる
- 再利用性の高いクラス作成に重要な“アクセス制御”
- “コンストラクタ”と初期化、本当に理解できてる?
- 継承やオーバーライドで簡単にクラスを“拡張”しよう
- 「static」でクラス共有の変数・メソッドを使いこなせ!
- Javaの実案件に必須のパッケージとインポートを知る
- プログラムを「変更」しやすくする“インターフェイス”
- Javaの参照型を文字列操作で理解して文法を総復習
- クラスの振る舞いを表すJavaの“メソッド”とは?
- 複雑なデータを表現できるクラスやフィールドって?
- データ集合を扱うのに便利なJavaの配列と拡張for文
- プログラミングの真骨頂! Javaで“反復処理”を覚える
- プログラミングの醍醐味! Javaで“条件式”を理解する
- Javaで一から理解するプログラムの変数と演算子
- Eclipse 3.4で超簡単Javaプログラミング基礎入門
Copyright © ITmedia, Inc. All Rights Reserved.