1. 序章

このチュートリアルでは、Javaの匿名クラスについて検討します。

それらのインスタンスを宣言および作成する方法について説明します。 また、それらのプロパティと制限についても簡単に説明します。

2. 匿名クラス宣言

匿名クラスは名前のない内部クラスです。名前がないため、匿名クラスのインスタンスを作成するために使用することはできません。 その結果、使用時に単一の式で匿名クラスを宣言してインスタンス化する必要があります。

既存のクラスを拡張するか、インターフェースを実装することができます。

2.1. クラスを拡張する

既存のクラスから匿名クラスをインスタンス化する場合、次の構文を使用します。

括弧内には、拡張するクラスのコンストラクターに必要なパラメーターを指定します。

new Book("Design Patterns") {
    @Override
    public String description() {
        return "Famous GoF book.";
    }
}

当然、親クラスのコンストラクターが引数を受け入れない場合は、括弧を空のままにしておく必要があります。

2.2. インターフェイスを実装する

インターフェイスから匿名クラスをインスタンス化することもできます。

明らかに、Javaのインターフェースにはコンストラクターがないため、括弧は常に空のままです。 これは、インターフェイスのメソッドを実装するために行う必要がある唯一の方法です。

new Runnable() {
    @Override
    public void run() {
        ...
    }
}

匿名クラスをインスタンス化したら、後でどこかで参照できるように、そのインスタンスを変数に割り当てることができます。

これは、Java式の標準構文を使用して実行できます。

Runnable action = new Runnable() {
    @Override
    public void run() {
        ...
    }
};

すでに述べたように、匿名クラス宣言は式であるため、ステートメントの一部である必要があります。 これは、ステートメントの最後にセミコロンを付けた理由を説明しています。

明らかに、インスタンスをインラインで作成すると、インスタンスを変数に割り当てることを回避できます。

List<Runnable> actions = new ArrayList<Runnable>();
actions.add(new Runnable() {
    @Override
    public void run() {
        ...
    }
});

特にrun()メソッドの実装に多くのスペースが必要な場合は、コードの可読性が低下しやすいため、この構文は細心の注意を払って使用する必要があります。

3. 匿名クラスのプロパティ

通常のトップレベルのクラスに関して、匿名クラスの使用には特定の特殊性があります。 ここでは、最も実用的な問題について簡単に触れます。 最も正確で更新された情報については、常にJava言語仕様を参照してください。

3.1. コンストラクタ

匿名クラスの構文では、複数のインターフェイスを実装させることはできません。 構築中、匿名クラスのインスタンスが1つだけ存在する可能性があります。 したがって、それらを抽象化することはできません。 名前がないため、拡張できません。 同じ理由で、匿名クラスは明示的に宣言されたコンストラクターを持つことはできません。

実際、コンストラクターがなくても、次の理由で問題は発生しません。

  1. 宣言すると同時に匿名クラスインスタンスを作成します
  2. 匿名のクラスインスタンスから、ローカル変数にアクセスし、クラスのメンバーを囲むことができます

3.2. 静的メンバー

匿名クラスは、定数であるものを除いて、静的メンバーを持つことはできません。

たとえば、これはコンパイルされません:

new Runnable() {
    static final int x = 0;
    static int y = 0; // compilation error!

    @Override
    public void run() {...}
};

代わりに、次のエラーが発生します。

The field y cannot be declared static in a non-static inner type, unless initialized with a constant expression

3.3. 変数のスコープ

匿名クラスは、クラスを宣言したブロックのスコープ内にあるローカル変数をキャプチャします。

int count = 1;
Runnable action = new Runnable() {
    @Override
    public void run() {
        System.out.println("Runnable with captured variables: " + count);
    }           
};

ご覧のとおり、ローカル変数countactionは同じブロックで定義されています。 このため、クラス宣言内からcountにアクセスできます。

ローカル変数を使用できるようにするには、それらが事実上finalである必要があることに注意してください。 JDK 8以降、キーワードfinalで変数を宣言する必要はなくなりました。 それでも、これらの変数はfinalである必要があります。 そうしないと、コンパイルエラーが発生します。

[ERROR] local variables referenced from an inner class must be final or effectively final

コンパイラが変数が実際には不変であるとコード内で判断するためには、変数に値を割り当てる場所は1つだけである必要があります。 効果的に最終的な変数の詳細については、記事「ラムダで使用されるローカル変数が最終的または効果的に最終的でなければならないのはなぜですか?」を参照してください。

すべての内部クラスと同様に、匿名クラスはそれを囲むクラスのすべてのメンバーにアクセスできることを述べておきます。

4. 匿名クラスのユースケース

匿名クラスのアプリケーションは多種多様である可能性があります。 考えられるいくつかのユースケースを見てみましょう。

4.1. クラス階層とカプセル化

アプリケーションでクラスのよりクリーンな階層を実現するには、一般的なユースケースでは内部クラスを使用し、非常に具体的なユースケースでは匿名クラスを使用する必要があります。 内部クラスを使用する場合、囲んでいるクラスのデータをより細かくカプセル化できる場合があります。 最上位クラスで内部クラス機能を定義する場合、それを囲むクラスは、そのメンバーの一部をpublicまたはpackageで表示できる必要があります。 当然のことながら、あまり評価されていない、あるいは受け入れられていない状況もあります。

4.2. よりクリーンなプロジェクト構造

一部のクラスのメソッドの実装をオンザフライで変更する必要がある場合は、通常、匿名クラスを使用します。 この場合、最上位クラスを定義するために、プロジェクトに新しい*。javaファイルを追加することを回避できます。 これは、その最上位クラスが1回だけ使用される場合に特に当てはまります。

4.3. UIイベントリスナー

グラフィカルインターフェイスを備えたアプリケーションでは、匿名クラスの最も一般的なユースケースは、さまざまなイベントリスナーを作成することです。 たとえば、次のスニペットでは次のようになります。

button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        ...
    }
}

インターフェイスActionListenerを実装する匿名クラスのインスタンスを作成します。そのactionPerformedメソッドは、ユーザーがボタンをクリックするとトリガーされます。

Java 8以降、ラムダ式の方が適しているようです。

5. 全体像

上記で検討した匿名クラスは、ネストされたクラスの特定のケースにすぎません。 一般に、ネストされたクラスは、別のクラスまたはインターフェイス内で宣言されたクラスです。

図を見ると、ローカルおよび非静的メンバークラスとともに匿名クラスがいわゆる内部クラスを形成していることがわかります。 静的メンバークラスとともに、ネストされたクラスを形成します。

6. 結論

この記事では、Java匿名クラスのさまざまな側面について検討しました。 ネストされたクラスの一般的な階層についても説明しました。

いつものように、完全なコードはGitHubリポジトリから入手できます。