連載
» 2010年07月27日 00時00分 公開

【改訂版】Eclipseではじめるプログラミング(17):あなたの知らない、4つのマニアックなJava文法 (3/3)

[小山博史,株式会社ガリレオ]
前のページへ 1|2|3       

【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]");
        }
    });
// 略
sample17/App7.javaに作った無名内部クラス

無名内部クラスは制限が多い

 このように、無名内部クラスはクラス名を指定せずに実装したいインターフェイスやクラスを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();
    }
}
sample17/App8.java

 このクラスでは、ネストしたインターフェイスとして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
App8.javaの実行結果

使いどころ

 複雑なデータ構造を内部に持ったり、たくさんのメソッドを持つようなクラスの場合は、無名内部クラスで実装すると可読性が落ちますし、メンテナンスもしにくいので、普通は採用しません。

 また、継承によって実装を再利用できませんから、コードを再利用したい場合に採用できません。しかし、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());
    }
// 略
sample17/App9.javaのコンストラクタ

 RemainViewクラスは無名内部クラスで実装しても構わないクラスでしたから、このようにローカル内部クラスで実装する利点が見当たらないので、ピンと来ないかもしれません。ローカル内部クラスとすることで、「どこからどこまでがひとまとまりなのか明確になる」という点だけ理解しておいてください。

使いどころ

 GUIアプリケーションなどでは、そこそこ複雑な初期化や処理を持つものの、コンストラクタ内でしか必要のないクラスが出てくる場面がよくあります。

 そういったクラスを無名内部クラスで実装すると、どこからどこまでがクラスの宣言なのかよく分からなくなってしまいます。そういったときに、ローカル内部クラスはよく使われます。

LLほどではないが、Javaでも軽量に

 前回、今回と、たくさん細かい話が出ましたが、ネストしたクラスについて理解できたでしょうか。初心者のうちは、「自分でネストした型を宣言して使う」機会は、それほど多くはないかもしれませんが、ほかの開発者のコードで見かけることはあるかと思います。そういったときのために、細かい点についても説明をしました。

 筆者の考えでは、初心者がネストした型を使う場面は、「同一ファイル内で必要になった時点でクラスを、その場で作成して、いろいろ試してみる」といったライトウェイトプログラミングをするときぐらいです。

 Javaでは、RubyやPythonといったLL(Lightweight Language、軽量プログラミング言語)ほど軽量なプログラミングはできませんが、前回と今回で紹介をしたネストした型や内部クラスをうまく利用することで、「思い付いたままに試行錯誤しながらプログラミングをする」といったことが手軽にできるようになります。

 試しに作ってみるようなプログラムでは、どんどん使って素早くやりたいことを実現してみましょう。もちろん、通常の開発でもネストした型は有用な場面はありますから、適材適所で利用できるようになりましょう。今回作ったサンプルのソースコードはこちらからダウンロードできます。

筆者紹介

小山博史(こやま ひろし)

情報家電、コンピュータと教育の研究に従事する傍ら、オープンソースソフトウェア、Java技術の普及のための活動を行っている。長野県の地域コミュニティである、SSS(G)bugs(J)の活動へも参加している。

著書に「基礎Java」(インプレス)、共著に「Javaコレクションフレームワーク」(ソフトバンククリエイティブ)、そのほかに雑誌執筆多数。



「【改訂版】Eclipseではじめるプログラミング」バックナンバー
前のページへ 1|2|3       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。