では、次にネストした型の1つであるメンバ・インターフェイスを使ってみましょう。そのために、さらにタイマーを改造してみます。ProgressViewクラスもResultViewクラスもupdateメソッドを持っています。画面出力関係を受け持つクラスについては、updateメソッドを実装することを強制して、1秒ごとに表示も更新するようにしてみましょう。
コンソールアプリケーションとしては、毎秒画面に出力されてしまうので使いにくくなりますが、GUIアプリケーションでは一定間隔で表示を更新する処理というのは、よく使う手法です。
sample16.App3をコピーして、sample16.App4を用意します。画面出力用クラスにupdateメソッド実装を強制するためには、updateメソッドだけを持つインターフェイスを用意し、それを実装したクラスを使って画面出力をする仕組みを導入します。ここでは、メンバ・インターフェイスObserverをsample16.App4クラス内に作成してみましょう。Eclipseでは、次のように作成します。
生成されたObserverインターフェイスにupdateメソッドの宣言を追加します。
interface Observer { void update(); }
ここで、このメンバ・インターフェイスは「static」が付いていませんが、暗黙のうちにstaticのインターフェイスとして宣言されています。ネストしたインターフェイスは常にstaticとなるので、覚えておきましょう。
なお、インターフェイスで宣言されたメソッドは暗黙のうちにpublicとなります。
次に、ResultViewクラスについて、作成したObserverインターフェイスのimplementsをします。すると、updateメソッドでエラーが発生してしまいます。先ほどは、publicを付けないメソッドも使えることを確認するために、わざと外しましたが、ここでは、メソッド宣言の修飾子にpublicを追加する必要があります。
class ResultView implements Observer { // (略) @Override public void update() { // (略) }
これはなぜかというと、Observerインターフェイスのupdateメソッドが暗黙のうちにpublicと宣言されているからです。
実は、publicをあえて外したResultViewクラスのupdateメソッドは、publicよりも制限が厳しいパッケージのアクセス制限となっていましたアクセス制限となっていました。しかし、Observerインターフェイスを実装すると、このアクセス制限を緩和するpublicとして宣言する必要が出てくるのです。
updateメソッドにpublicを付けたら、Observerインターフェイスのupdateメソッドをオーバーライドしていることが分かるように、「@Override」の行も追加します。@Overrideは、Observerのupdateメソッドをオーバーライドしていることを意味するアノテーションです。アノテーションについては、別途説明する予定です。ここでは呪文だと思って付けておきましょう。
次に、ProgressViewクラスの変更です。こちらもResultViewクラスと同様にObserverインターフェイスのimplementsをします。@Overrideの行も同様に追加します。
1秒ごとに表示を更新するとなると、これまでの表示方法では分かりにくくなるので、出力内容も変更します。「.」ではなく、経過時間を表示するようにします。
class ProgressView implements Observer { int count = 0; String printValue = ""; public void countUp(int v) { count += v; printValue = String.valueOf(count); } @Override public void update() { System.out.print("\n\n経過時間:"); System.out.print(printValue + "[sec]"); } }
「Observerへ表示の更新を通知する」という役割について責任を持つクラスとして「Observalbe」というクラスを用意します。これも、sample16.App4のメンバ・クラスとして追加します。このクラスはObserverインターフェイスを実装したクラスをjava.util.List型のobserversフィールドへ保存します。
notifyObserversメソッドが呼び出された場合に、observersに登録されているObserverオブジェクトすべてについてupdateメソッドを呼び出して、画面出力をしています。observersへのObserverオブジェクト登録のために、addObserverメソッドを持ちます。本来なら、Observerオブジェクト登録解除のためのメソッドも用意するべきですが、ここでは使用しないので用意していません。
class Observable { java.util.List observers = new java.util.LinkedList(); void addObserver(Observer o) { observers.add(o); } void notifyObservers() { for (int i=0 ; i<observers.size() ; i++) { Observer o = (Observer)observers.get(i); o.update(); } } }
ObserverインターフェイスやObservableクラスを用意することにより、指示を出す側は、ObservableのnotifyObserversメソッドの呼び出しをすれば表示が更新できることになり、「そこにどんなObserverオブジェクトが登録されているか」まで気にする必要がなくなります。
また、Observerを実装するクラスへは、そのクラスが表示すべき内容についてのみコーディングすればよいことになり、依存関係が単純になります。
最後に、sample16.App4クラスはsample16.App3クラスと比較して次の変更を行っています。変更した行には「//」を付けています。
package sample16; public class App4 { // Observer, Observable, ProgressView, ResultViewは省略(前述参照) final static long INTERVAL = 1000; ResultView resultView = new ResultView(); ProgressView progressView = new ProgressView(); Observable view = new Observable(); // int count; // コンストラクタ App4() { count = 12; resultView.setStartValue(count); view.addObserver(progressView); // view.addObserver(resultView); // } public void execute() throws Exception { resultView.setStartTime(System.currentTimeMillis()); while (count > 0) { Thread.sleep(INTERVAL); count--; progressView.countUp(1); view.notifyObservers(); // } resultView.setStopTime(System.currentTimeMillis()); view.notifyObservers(); // } // 略 }
sample16.App4を実行すると、次のようになります。経過時間が「12[sec]」のものが2つ表示されていて「おやっ?」と思うかもしれませんが、これはカウント終了後に終了時刻が確定してから画面表示があるためで、プログラム通りに動作していることが分かります。
経過時間:1[sec] 開始時刻:1272766968273 カウント:12 終了時刻:0 (略) 経過時間:12[sec] 開始時刻:1272766968273 カウント:12 終了時刻:0 経過時間:12[sec] 開始時刻:1272766968273 カウント:12 終了時刻:1272766980302
さて、どうでしょうか。メンバ・インターフェイスも、クラス内で宣言されているというだけで、これまで使用してきたインターフェイスと同じような感じで利用ができます。
メンバ・クラスは、通常のクラスやインターフェイスと同じように使えることが理解できたでしょうか。ただし、エンクロージング型以外のクラスから使用する場合には、いくつかの注意点があります。それらについては次回説明しますので、ここでは「ネストした型とは、エンクロージング型の中に宣言された型」という点を理解できれば十分です。
ネストしたインターフェイスは、常にstaticとなる点を覚えておきましょう。ネストしたインターフェイスについては、通常のインターフェイスと同じように使えます。
メンバ・クラスやメンバ・インターフェイスは、基本的にはエンクロージング型と関係が深く、常に一緒に使いたいものがある場合に利用します。そうすることで、内部的にまとめておきたい情報を同一ファイル内に入れておくことができるので、クラスの整理が楽になることがあります。
今回のサンプルプログラムのように、同名のクラスが何度も出てくるような場合、ネストしたクラスやインターフェイスが使えないと、それぞれにパッケージを用意する必要が出てきます。プロトタイプを作っていたり、サンプルプログラムを作成したりしているときは、今回のような手順でクラスを追加したり、削除したりして、試行錯誤をしたくなります。そんなときは、ネストしたクラスやインターフェイスを使って、軽量プログラミングをすればいいでしょう。
ネストした型を使ったぐらいで軽量プログラミングというのは大げさな感じもありますが、ネストした型の利用によってソースファイルの数は確実に減ります。今回紹介しているクラスもそれなりの数になりますが、ネストした型を使ったおかげで、ソースファイルの数は非常に少なくて済みました。「同じソースファイル内に関係するクラスが含まれていて手軽に参照したり修正したりできる」というのは、大きなメリットです。
ネストした型について、今回紹介した例は一部です。まだ説明が足りていない部分としては、参照できる変数の範囲や、無名内部クラスやローカル内部クラス、staticのネストしたクラスといったものがあります。次回は、これらについて説明する予定です。今回作ったサンプルのソースコードはこちらからダウンロードできます。
小山博史(こやま ひろし)
情報家電、コンピュータと教育の研究に従事する傍ら、オープンソースソフトウェア、Java技術の普及のための活動を行っている。長野県の地域コミュニティである、SSS(G)やbugs(J)の活動へも参加している。
著書に「基礎Java」(インプレス)、共著に「Javaコレクションフレームワーク」(ソフトバンククリエイティブ)、そのほかに雑誌執筆多数。
Copyright © ITmedia, Inc. All Rights Reserved.