
“ネスト”した型で始める軽量Javaプログラミング!?
株式会社ガリレオ
小山博史
2010/5/20
メンバ・インターフェイスも使ってみよう
では、次にネストした型の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メソッドの宣言を追加します。
App4のネストしたインターフェイス「Observer」 interface Observer {
void update();
}
■ ネストしたインターフェイスは常に暗黙でstaticとなる
ここで、このメンバ・インターフェイスは「static」が付いていませんが、暗黙のうちにstaticのインターフェイスとして宣言されています。ネストしたインターフェイスは常にstaticとなるので、覚えておきましょう。
なお、インターフェイスで宣言されたメソッドは暗黙のうちにpublicとなります。
■ 暗黙のpublicは、宣言したpublicよりもアクセス制限が厳しい
次に、ResultViewクラスについて、作成したObserverインターフェイスのimplementsをします。すると、updateメソッドでエラーが発生してしまいます。先ほどは、publicを付けないメソッドも使えることを確認するために、わざと外しましたが、ここでは、メソッド宣言の修飾子にpublicを追加する必要があります。
App4のネストしたクラス「ResultView」 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秒ごとに表示を更新するとなると、これまでの表示方法では分かりにくくなるので、出力内容も変更します。「.」ではなく、経過時間を表示するようにします。
App4のネストしたクラス「ProgressView」 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オブジェクト登録解除のためのメソッドも用意するべきですが、ここでは使用しないので用意していません。
App4のネストしたクラス「Observable」 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つ表示されていて「おやっ?」と思うかもしれませんが、これはカウント終了後に終了時刻が確定してから画面表示があるためで、プログラム通りに動作していることが分かります。
App4.javaの実行結果例
経過時間:1[sec]
開始時刻:1272766968273
カウント:12
終了時刻:0
(略)
経過時間:12[sec]
開始時刻:1272766968273
カウント:12
終了時刻:0
経過時間:12[sec]
開始時刻:1272766968273
カウント:12
終了時刻:1272766980302
さて、どうでしょうか。メンバ・インターフェイスも、クラス内で宣言されているというだけで、これまで使用してきたインターフェイスと同じような感じで利用ができます。
次回は、無名内部クラスやローカル内部クラス
メンバ・クラスは、通常のクラスやインターフェイスと同じように使えることが理解できたでしょうか。ただし、エンクロージング型以外のクラスから使用する場合には、いくつかの注意点があります。それらについては次回説明しますので、ここでは「ネストした型とは、エンクロージング型の中に宣言された型」という点を理解できれば十分です。
ネストしたインターフェイスは、常にstaticとなる点を覚えておきましょう。ネストしたインターフェイスについては、通常のインターフェイスと同じように使えます。
メンバ・クラスやメンバ・インターフェイスは、基本的にはエンクロージング型と関係が深く、常に一緒に使いたいものがある場合に利用します。そうすることで、内部的にまとめておきたい情報を同一ファイル内に入れておくことができるので、クラスの整理が楽になることがあります。
今回のサンプルプログラムのように、同名のクラスが何度も出てくるような場合、ネストしたクラスやインターフェイスが使えないと、それぞれにパッケージを用意する必要が出てきます。プロトタイプを作っていたり、サンプルプログラムを作成したりしているときは、今回のような手順でクラスを追加したり、削除したりして、試行錯誤をしたくなります。そんなときは、ネストしたクラスやインターフェイスを使って、軽量プログラミングをすればいいでしょう。
ネストした型を使ったぐらいで軽量プログラミングというのは大げさな感じもありますが、ネストした型の利用によってソースファイルの数は確実に減ります。今回紹介しているクラスもそれなりの数になりますが、ネストした型を使ったおかげで、ソースファイルの数は非常に少なくて済みました。「同じソースファイル内に関係するクラスが含まれていて手軽に参照したり修正したりできる」というのは、大きなメリットです。
ネストした型について、今回紹介した例は一部です。まだ説明が足りていない部分としては、参照できる変数の範囲や、無名内部クラスやローカル内部クラス、staticのネストしたクラスといったものがあります。次回は、これらについて説明する予定です。今回作ったサンプルのソースコードはこちらからダウンロードできます。
■ @IT関連記事
| Java SE コアAPI 使用コード例一覧 Java開発者/プログラマのための、Java SEコアAPIの使用コード例の記事へのJavadocっぽいリンク集。メソッドやコンストラクタ、例外などAPIの使い方の参考にしてください 「Java Solution」フォーラム 2009/3/24 |
| 筆者プロフィール |
| 小山博史(こやま ひろし) 情報家電、コンピュータと教育の研究に従事する傍ら、オープンソースソフトウェア、Java技術の普及のための活動を行っている。長野県の地域コミュニティである、SSS(G)やbugs(J)の活動へも参加している。 著書に「基礎Java」(インプレス)、共著に「Javaコレクションフレームワーク」(ソフトバンククリエイティブ)、そのほかに雑誌執筆多数。 |
| Index | ||||||||
|
||||||||
【改訂版】Eclipseではじめるプログラミング バックナンバー 連載インデックスへ»
- 第1回 Eclipse 3.4で超簡単Javaプログラミング基礎入門
- 第2回 Javaで一から理解するプログラムの変数と演算子
- 第3回 プログラミングの醍醐味! Javaで“条件式”を理解する
- 第4回 プログラミングの真骨頂! Javaで“反復処理”を覚える
- 第5回 データ集合を扱うのに便利なJavaの配列と拡張for文
- 第6回 複雑なデータを表現できるクラスやフィールドって?
- 第7回 クラスの振る舞いを表すJavaの“メソッド”とは?
- 第8回 Javaの参照型を文字列操作で理解して文法を総復習
- 第9回 プログラムを「変更」しやすくする“インターフェイス”
- 第10回 Javaの実案件に必須のパッケージとインポートを知る
- 第11回 「static」でクラス共有の変数・メソッドを使いこなせ!
- 第12回 継承やオーバーライドで簡単にクラスを“拡張”しよう
- 第13回 “コンストラクタ”と初期化、本当に理解できてる?
- 第14回 再利用性の高いクラス作成に重要な“アクセス制御”
- 第15回 Javaは「抽象クラス」で実装を上手に再利用できる
- 第16回 “ネスト”した型で始める軽量Javaプログラミング!?
- 第17回 あなたの知らない、4つのマニアックなJava文法
- 第18回 強く型付けされているJavaの理解に必修の“型変換”
- 第19回 キュー構造をJavaで実装してジェネリック型を理解する
- 第20回 拡張for文の真の実力を知り、反復処理を使いこなせ
- 第21回 7ステップで理解するJavaでの列挙型/enum使用法
- 第22回 いまさら聞けない「Javadoc」と「アノテーション」入門
- 第23回 プログラマの宿命! 例外とエラー処理を理解する
- 第24回 Javaの例外処理で知らないと損する7つのテクニック
| Java Solution全記事一覧 |
TechTargetジャパン
- Scalaのパッケージ、アクセス修飾子、オブジェクト継承 (2012/5/22)
インポート、パッケージオブジェクト、抽象クラス/抽象メソッド、オーバーライド、final、シールドクラスなども - 基幹系システムでCloud SQLは使えるか試してみた (2012/5/17)
サンプルとしてMRPシステムを作成して動かし、「再帰呼び出し」などのパフォーマンスを測定して検証してみます - アジャイル管理ツール9選+Pivotal Tracker入門 (2012/5/14)
群雄割拠のアジャイルプロジェクト管理ツールを9つ紹介し、特に注目を集めているPivotal Trackerの基本的な使い方を解説します - サーバサイドJSやJavaでWebアプリが作れるXPages (2012/5/11)
Notes/Dominoの資産をサーバサイドJavaScriptやJavaで操作し、HTMLやJavaScript、CSSをUIにできる技術を紹介
|
|
キャリアアップ
スポンサーからのお知らせ
- - PR -
イベントカレンダー
- - PR -
