本ページはプロモーションが含まれています

Java入門

【Java】匿名クラス(無名クラス)とは?使い方とラムダ式との違いを解説

トム

・都内自社開発IT企業勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

Javaのコードを読んでいると、時々「newの後にいきなりクラスの中身を書き始める」ような不思議な記述に出会うことはありませんか。

私自身、10年以上Javaで開発をしていますが、若手の頃はこの構文の意味が分からず、首をかしげた経験があります。これは「匿名クラス(無名クラス)」と呼ばれる機能で、Javaプログラミングの表現力を高めてくれる便利なテクニックの一つです。

しかし、その便利さとは裏腹に、使い方を間違えるとコードが途端に読みにくくなる諸刃の剣でもあります。

この記事を読めば、以下の悩みを解決できます。

  • Javaの匿名クラス(無名クラス)が何なのか、基本から理解したい方
  • 匿名クラスがどのような場面で役立つのか、具体的な使い方を知りたい方
  • Java8から導入されたラムダ式と匿名クラスの違いが分からず、使い分けに悩んでいる方

この記事では、Javaの匿名クラスの基礎から実践的な使い方、そしてラムダ式との上手な付き合い方まで、丁寧に解説していきます。

無名クラス(匿名クラス)とは?Javaでの定義と特徴

Javaの匿名クラス(無名クラス)とは、その名の通り名前を持たないクラスのことです。

通常のクラスのようにclass MyClass { ... }と名前を付けて定義するのではなく、インスタンスを生成するその場で、一度きりの使い捨てクラスとして定義します。

この「その場限り」という性質が、匿名クラスの最大の特徴であり、メリットでもあります。

クラスに「名前がない」とはどういう意味か

クラスに名前がないとは、クラスを定義する際にclassキーワードの後ろにクラス名を記述しない、ということです。

例えば、MyRunnableという名前のクラスを作る場合、以下のように書きますよね。

// 名前付きの通常のクラス定義
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("スレッドを実行します。");
    }
}

// インスタンス化
Runnable runner = new MyRunnable();

一方、匿名クラスを使うと、MyRunnableという名前はどこにも登場しません。

// 匿名クラスを使ったインスタンス化
Runnable runner = new Runnable() {
    @Override
    public void run() {
        System.out.println("スレッドを実行します。");
    }
};

このように、new Runnable()の直後に{}ブロックを続け、インターフェースのメソッドをその場で実装しています。この{}で囲まれた部分が、名前のないクラスの定義そのものなのです。

通常のクラスとの違い

匿名クラスと通常のクラスの違いをまとめると、以下のようになります。

項目通常のクラス匿名クラス(無名クラス)
名前ありなし
再利用性何度でもインスタンス化できる一度しかインスタンス化できない
コンストラクタ独自のコンストラクタを定義できる独自のコンストラクタは定義できない
実装複数のインターフェースを実装できる一つのクラス継承か一つのインターフェース実装のみ
ファイルクラス名.javaとして独立したファイル他のクラスファイル内に定義される

最も大きな違いは再利用性です。匿名クラスは、その場で定義してすぐに使う「インスタント」なクラスなので、別の場所で同じものを使いたくなった場合は、通常の名前付きクラスとして定義しなおす必要があります。

どんな場面で使われるのか(主な用途)

Javaの匿名クラスは、特定の場所で一度だけしか使わないような、ごく小規模な処理を実装する場合に非常に役立ちます。

主な用途は以下の3つです。

用途

  1. イベントリスナーの実装GUIプログラミングで、ボタンがクリックされた時の処理などを記述する際によく使われます。
  2. スレッドの処理内容の定義ThreadクラスやRunnableインターフェースを使って、新しいスレッドで実行したい処理を簡潔に書けます。
  3. テスト用の簡易的なオブジェクト生成テストコードの中で、インターフェースを実装した一時的なオブジェクト(モックやスタブ)を作る場合に便利です。

いずれのケースも、「この処理はこの場所専用で、他では使わない」という共通点があります。わざわざ新しいファイルを作ってクラスを定義するまでもない、ちょっとした処理を埋め込むのに最適なのが、このJava匿名クラスなのです。

Javaでの無名クラスの書き方

匿名クラスの概念が分かったところで、具体的な書き方を見ていきましょう。構文自体はシンプルなので、ルールさえ覚えてしまえばすぐに使いこなせます。

基本構文と記述ルール

Javaの匿名クラスは、以下の構文で記述します。

new 親クラス名 or インターフェース名() {
    // フィールドの定義
    // メソッドのオーバーライドや追加
};

ポイントは、new演算子の後に、継承するクラス名か実装するインターフェース名を書き、その直後の()に続けて{}ブロックでクラスの中身を定義する点です。

文の終わりにはセミコロン;が必要な点に注意してください。これは、匿名クラスの定義全体が、一つの式(インスタンス生成式)として扱われるためです。

インターフェース実装時の無名クラス

インターフェースを実装する際の匿名クラスは、最もよく見かけるパターンです。Runnableインターフェースを例に見てみましょう。Runnableにはrun()というメソッドが一つだけ定義されています。

// Runnableインターフェースを実装する匿名クラス
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        // ここにスレッドで実行したい処理を書く
        for (int i = 0; i < 3; i++) {
            System.out.println("匿名クラスで実行中...");
        }
    }
};

// スレッドを生成して実行
Thread thread = new Thread(runnable);
thread.start();

このコードでは、new Runnable() { ... }の部分でRunnableインターフェースを実装した名前のないクラスを定義し、同時にそのインスタンスを生成しています。生成されたインスタンスはrunnable変数に代入され、Threadのコンストラクタに渡されています。

抽象クラス継承時の無名クラス

抽象クラスを継承する場合も、インターフェースの場合とほとんど同じです。継承したクラスの抽象メソッドを、{}ブロックの中で実装(オーバーライド)します。

例えば、Animalという抽象クラスがあったとします。

// 鳴き声を出力する抽象メソッドを持つ抽象クラス
abstract class Animal {
    abstract void cry();
}

このAnimalクラスを継承する匿名クラスは、以下のように書けます。

// Animalクラスを継承する匿名クラス
Animal dog = new Animal() {
    @Override
    void cry() {
        System.out.println("ワン!");
    }
};

dog.cry(); // "ワン!" と出力される

Animal cat = new Animal() {
    @Override
    void cry() {
        System.out.println("ニャー");
    }
};

cat.cry(); // "ニャー" と出力される

このように、その場ですぐに異なる実装を持つオブジェクトを手軽に作れるのが、Java匿名クラスの強みです。

無名クラスの使用例

それでは、実際の開発現場で匿名クラスがどのように使われているのか、より具体的なコード例を見ていきましょう。

ボタンイベント(ActionListener)での利用例

GUIアプリケーションでボタンがクリックされたときの動作を定義するActionListenerは、匿名クラスが活躍する代表的な場面です。

javax.swingパッケージを使った簡単な例を示します。

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class ButtonExample {
    public static void main(String[] args) {
        JFrame frame = new JFrame("匿名クラスの例");
        JButton button = new JButton("クリックしてね");

        // ボタンがクリックされたときの処理を匿名クラスで実装
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(frame, "ボタンがクリックされました!");
            }
        });

        frame.getContentPane().add(button);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(300, 200);
        frame.setVisible(true);
    }
}

button.addActionListener()の引数に、ActionListenerインターフェースを実装した匿名クラスを直接渡しています。このボタンのためだけの処理なので、わざわざ名前付きのクラスを作るよりもコードがシンプルにまとまりますね。

スレッド(Runnable)を使う場合の例

先ほども少し触れましたが、Runnableインターフェースを使ったスレッド処理も、Javaの無名クラスが頻繁に使われる場面です。

public class ThreadExample {
    public static void main(String[] args) {
        System.out.println("メインスレッドを開始します。");

        // Runnableを実装する匿名クラスをThreadのコンストラクタに直接渡す
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("別スレッドを開始します。");
                    Thread.sleep(2000); // 2秒待機
                    System.out.println("別スレッドを終了します。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread.start(); // スレッドの実行を開始

        System.out.println("メインスレッドを終了します。");
    }
}

この例では、new Thread()の中に直接new Runnable() { ... }を記述しています。これにより、一時的な処理のためだけにRunnableを実装したクラスを別途定義する手間が省け、コードが非常にコンパクトになります。

簡易的なオブジェクト生成での例

例えば、ソート機能の順序をカスタマイズするためにComparatorインターフェースを使う場合も、匿名クラスが便利です。

Userクラスのリストを、年齢(age)でソートする例を見てください。

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

class User {
    String name;
    int age;
    User(String name, int age) { this.name = name; this.age = age; }
    @Override
    public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; }
}

public class ComparatorExample {
    public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        userList.add(new User("Taro", 30));
        userList.add(new User("Jiro", 25));
        userList.add(new User("Saburo", 35));

        // 年齢でソートするためのComparatorを匿名クラスで実装
        Collections.sort(userList, new Comparator<User>() {
            @Override
            public int compare(User u1, User u2) {
                return Integer.compare(u1.age, u2.age);
            }
        });

        System.out.println(userList);
        // 出力: [User{name='Jiro', age=25}, User{name='Taro', age=30}, User{name='Saburo', age=35}]
    }
}

Collections.sort()の第2引数に、Comparatorをその場で実装した匿名クラスを渡しています。このソート順はこの場所でしか使わないため、匿名クラスが最適な選択肢となります。

無名クラスとラムダ式の違い

Java8(Java 8)でラムダ式が登場したことで、匿名クラスの使われ方は大きく変わりました。ラムダ式は、特定の条件下で匿名クラスをよりシンプルに書くためのものです。

Java8以降で登場したラムダ式との関係

ラムダ式は、実装すべきメソッドが一つだけのインターフェース(関数型インターフェース)の実装を、非常に簡潔に記述できる構文です。

先ほどのRunnableの例を思い出してください。

// 匿名クラスの場合
Runnable runnable1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("実行します。");
    }
};

// ラムダ式の場合
Runnable runnable2 = () -> System.out.println("実行します。");

() -> System.out.println("実行します。")の部分がラムダ式です。メソッド名や@Overrideアノテーションなどがなくなり、引数リスト()と処理内容{}(1行の場合は省略可)だけで記述できています。

これは、Runnablerun()というメソッドを一つしか持たない関数型インターフェースだからこそ可能な記法です。コンパイラが文脈から判断して、このラムダ式がRunnableインターフェースのrun()メソッドの実装であると解釈してくれます。

あわせて読む

どちらを使うべきかの判断基準

匿名クラスとラムダ式のどちらを使うべきか、判断基準はとてもシンプルです。

  • ラムダ式を使うべき場合:
    • 関数型インターフェース(抽象メソッドが1つだけのインターフェース)を実装するとき。
    • ActionListener, Runnable, Comparatorなど、多くのケースが該当します。
  • 匿名クラスを使うべき場合:
    • 実装(オーバーライド)すべきメソッドが複数あるインターフェースや抽象クラスを扱うとき。
    • クラス内でフィールド(メンバ変数)や、オーバーライド以外の追加メソッドを定義したいとき。
    • thisキーワードが、匿名クラスのインスタンス自身を指す必要があるとき。(ラムダ式の場合、thisは外側のクラスのインスタンスを指します)

基本的には「ラムダ式で書けるなら、ラムダ式を使う」のが現代のJavaプログラミングの主流です。その方がコードが短く、すっきりするためです。

可読性・メンテナンス性の比較

シンプルな処理であれば、ラムダ式の方が圧倒的に可読性が高くなります。

// 匿名クラス
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("クリック!");
    }
});

// ラムダ式
button.addActionListener(e -> System.out.println("クリック!"));

一目瞭然ですね。ラムダ式は処理の本質だけを抜き出したような記述が可能です。

一方で、処理が複雑になってくると話は変わります。ラムダ式の中に何十行ものコードを書くと、かえって読みにくくなる場合があります。そのような場合は、匿名クラスを使うか、あるいは処理をメソッドとして切り出してメソッド参照(例: this::handleClick)を使う方が、メンテナンス性は向上します。

無名クラスを使うときの注意点

Javaの匿名クラスは便利ですが、いくつか注意すべき点があります。これらを知らずに多用すると、後でコードの保守に苦労するかもしれません。

ネストが深くなりやすい問題

匿名クラスの処理の中に、さらに別の匿名クラスを記述する…といった入れ子(ネスト)構造も可能です。しかし、これはコードの可読性を著しく低下させる原因になります。

// ネストが深くなった悪い例
someMethod(new SomeInterface() {
    @Override
    public void execute() {
        anotherMethod(new AnotherInterface() {
            @Override
            public void process() {
                // 処理がどんどん内側に入っていく…
                // インデントが深くなり、非常に読みにくい
            }
        });
    }
});

インデントが深くなりすぎたコードは、処理の流れを追うのが困難です。ネストが2段階以上になるようなら、ラムダ式やメソッドへの切り出しを検討すべきです。

デバッグ・再利用の難しさ

匿名クラスには名前がないため、デバッグ時に少し困ることがあります。例外が発生したときのスタックトレースには、OuterClassName$1のように「外側のクラス名$数字」という形で表示されます。どの匿名クラスで問題が起きたのか、特定しにくい場合があるのです。

また、匿名クラスは定義したその場でしか使えません。もし複数の場所で同じような処理が必要になった場合は、コピペでコードを増やすのではなく、きちんと名前の付いた通常のクラスとして再定義(リファクタリング)しましょう。

リファクタリング時の対処法

もし、あなたがメンテナンスしているコードに、肥大化してしまった複雑な匿名クラスがあったらどうすればよいでしょうか。

その場合の対処法は「クラスの抽出 (Extract Class)」というリファクタリング手法です。

  1. 匿名クラスで行っている処理を、新しい独立したクラスとして定義します。
  2. 元のコードでは、その新しいクラスのインスタンスを生成するように書き換えます。

これにより、処理に適切な名前が与えられ、単体でのテストも容易になります。コードの見通しが良くなり、再利用性も高まるでしょう。IDE(統合開発環境)の多くは、このリファクタリングを自動で行う機能を持っています。

まとめ:無名クラスは「一度きりの即席オブジェクト」

Javaの匿名クラス(無名クラス)は、特定の場所で一度だけ使用するオブジェクトを、その場で手軽に定義するための機能です。

イベントリスナーやスレッド処理など、「使い捨て」のロジックを実装する際にコードを簡潔に保つ効果があります。

匿名クラスを理解するとラムダ式も理解しやすい

Java8から導入されたラムダ式は、この匿名クラスの考え方がベースにあります。匿名クラスが「メソッドを実装した、名前のないオブジェクト」であるのに対し、ラムダ式はそこからさらに定型的な記述を省略した「処理そのもの」を記述する仕組みです。

そのため、匿名クラスの概念をきちんと理解しておくことが、ラムダ式をより深く、正しく使いこなすための近道になります。

シンプルな実装と明確な責務を意識しよう

匿名クラスは強力ですが、乱用は禁物です。処理が数行に収まるようなシンプルなケースに限定して使用するのが良いでしょう。

もし匿名クラスのコードが長くなってきたら、それはクラスとして独立させるべきサインかもしれません。常にコードの可読性を第一に考え、それぞれの機能が持つべき責務を明確にすることを心がけて、Javaプログラミングを楽しんでいきましょう。

  • この記事を書いた人
  • 最新記事

トム

・都内自社開発IT企業勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

-Java入門