【改訂版】Eclipseではじめるプログラミング
連載インデックスへ
【改訂版】Eclipseではじめるプログラミング(16)

“ネスト”した型で始める軽量Javaプログラミング!?


株式会社ガリレオ
小山博史
2010/5/20


メンバ・インターフェイスも使ってみよう

 では、次にネストした型の1つであるメンバ・インターフェイスを使ってみましょう。そのために、さらにタイマーを改造してみます。ProgressViewクラスもResultViewクラスもupdateメソッドを持っています。画面出力関係を受け持つクラスについては、updateメソッドを実装することを強制して、1秒ごとに表示も更新するようにしてみましょう。

 コンソールアプリケーションとしては、毎秒画面に出力されてしまうので使いにくくなりますが、GUIアプリケーションでは一定間隔で表示を更新する処理というのは、よく使う手法です。

Eclipseでエンクロージング型のインターフェイスを作成

 sample16.App3をコピーして、sample16.App4を用意します。画面出力用クラスにupdateメソッド実装を強制するためには、updateメソッドだけを持つインターフェイスを用意し、それを実装したクラスを使って画面出力をする仕組みを導入します。ここでは、メンバ・インターフェイスObserverをsample16.App4クラス内に作成してみましょう。Eclipseでは、次のように作成します。

  1. Eclipseでsample16のApp4.javaを選択
  2. マウスの右ボタンをクリックして表示されるメニューで[新規]→[インターフェース]を指定
  3. 表示される[新規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メソッドを呼び出すように変更
  sample16/App4.java
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コレクションフレームワーク」(ソフトバンククリエイティブ)、そのほかに雑誌執筆多数。

1-2-3
 

 Index
第16回 “ネスト”した型で始める軽量Javaプログラミング!?
  Page1
通常のJavaプログラミングよりも“軽量”に
ネストした型(クラス/インターフェイス)とは
サンプル「タイマーアプリ」概要
タイマーアプリに出力機能を追加
  Page2
出力機能をメンバ・クラスとして実装
コラム 「“$”を含むクラスって何?」
Page3
メンバ・インターフェイスも使ってみよう
次回は、無名内部クラスやローカル内部クラス

【改訂版】Eclipseではじめるプログラミング バックナンバー 連載インデックスへ»



Java Solution全記事一覧

TechTargetジャパン

Java Solution フォーラム 新着記事

@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

RSSフィード

キャリアアップ

- PR -
@IT Sepcial

イベントカレンダー

PickUpイベント

- PR -
もっと見る
- PR -

お勧め求人情報

ホワイトペーパーTechTargetジャパン

@IT Sepcial
ソリューションFLASH