“ネスト”した型で始める軽量Javaプログラミング!?:【改訂版】Eclipseではじめるプログラミング(16)(3/3 ページ)
これからプログラミングを学習したい方、Javaは難しそうでとっつきづらいという方のためのJavaプログラミング超入門連載です。最新のEclipse 3.4とJava 6を使い大幅に情報量を増やした、連載「Eclipseではじめるプログラミング」の改訂版となります
メンバ・インターフェイスも使ってみよう
では、次にネストした型の1つであるメンバ・インターフェイスを使ってみましょう。そのために、さらにタイマーを改造してみます。ProgressViewクラスもResultViewクラスもupdateメソッドを持っています。画面出力関係を受け持つクラスについては、updateメソッドを実装することを強制して、1秒ごとに表示も更新するようにしてみましょう。
コンソールアプリケーションとしては、毎秒画面に出力されてしまうので使いにくくなりますが、GUIアプリケーションでは一定間隔で表示を更新する処理というのは、よく使う手法です。
Eclipseでエンクロージング型のインターフェイスを作成
sample16.App3をコピーして、sample16.App4を用意します。画面出力用クラスにupdateメソッド実装を強制するためには、updateメソッドだけを持つインターフェイスを用意し、それを実装したクラスを使って画面出力をする仕組みを導入します。ここでは、メンバ・インターフェイスObserverをsample16.App4クラス内に作成してみましょう。Eclipseでは、次のように作成します。
- Eclipseでsample16のApp4.javaを選択
- マウスの右ボタンをクリックして表示されるメニューで[新規]→[インターフェース]を指定
- 表示される[新規Javaインターフェース]ダイアログで、[エンクロージング型]をチェックし、[名前]に[Observer]、[修飾子]に「default」を指定し、[終了]をクリック
生成されたObserverインターフェイスにupdateメソッドの宣言を追加します。
interface Observer { void update(); }
ネストしたインターフェイスは常に暗黙でstaticとなる
ここで、このメンバ・インターフェイスは「static」が付いていませんが、暗黙のうちにstaticのインターフェイスとして宣言されています。ネストしたインターフェイスは常にstaticとなるので、覚えておきましょう。
なお、インターフェイスで宣言されたメソッドは暗黙のうちにpublicとなります。
暗黙のpublicは、宣言した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メソッドをオーバーライドしていることを意味するアノテーションです。アノテーションについては、別途説明する予定です。ここでは呪文だと思って付けておきましょう。
メンバ・インターフェイスをもう1つ実装
次に、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を実装するクラスへは、そのクラスが表示すべき内容についてのみコーディングすればよいことになり、依存関係が単純になります。
App4クラス全体のコードを確認
最後に、sample16.App4クラスはsample16.App3クラスと比較して次の変更を行っています。変更した行には「//」を付けています。
- Observable型のviewフィールドを追加
- コンストラクタに、viewへresultViewとprogressViewを登録する処理を追加
- 表示処理は、viewのnotifyObserversメソッドを呼び出すように変更
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(); // } // 略 }
App4を実行すると
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コレクションフレームワーク」(ソフトバンククリエイティブ)、そのほかに雑誌執筆多数。
- 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.