記事内にはプロモーションが含まれています

【Java】無名クラス(匿名クラス)とは?書き方からラムダ式への変換まで徹底解説

【Java】無名クラス(匿名クラス)とは?書き方からラムダ式への変換まで徹底解説 Java

Javaのコードを読んでいると、new インターフェース名() { ... } という不思議な記述に出会うことがあります。

クラスを定義しているようにも見えるし、インスタンス化しているようにも見えるこの構文こそが「無名クラス(匿名クラス)」です。

「クラスなのに名前がないの?」「どういう時に使うの?」と疑問に思う方も多いでしょう。

特にJava 8以降は「ラムダ式」が登場したことで、無名クラスの出番は減ったと言われていますが、Android開発やGUIアプリ、レガシーなシステムの保守においては依然として重要な知識です。

この記事では、Javaにおける無名クラスの仕組みから基本的な書き方、ラムダ式への書き換え方法、そして実務での使いどころまでをわかりやすく解説します。

【本記事の信頼性】
プロフィール
執筆者:マヒロ
  • 執筆者は元エンジニア
  • SES⇒大手の社内SE⇒独立
  • 現在はこじんまりとしたプログラミングスクールを運営
  • モットーは「利他の精神」

無名クラス(匿名クラス)とは何か?

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

通常、クラスを利用するには class MyClass { ... } のように名前を付けて定義し、その後に new MyClass() としてインスタンス化します。

しかし、無名クラスを使うと、「クラスの定義」と「インスタンス化(オブジェクトの生成)」を同時に行うことができます。

「その場限りの使い捨てクラス」

無名クラスの最大の特徴は、「その場所でしか使わない、使い捨てのクラス」を簡潔に書ける点です。

「一度しか使わないのに、わざわざ別ファイルを作ってクラス名を考えるのは面倒だ」という場面で非常に役に立ちます。

インターフェースや抽象クラスを「直接」インスタンス化する

Javaでは、インターフェースや抽象クラスはそのままでは new できません。
通常は、それらを実装・継承したサブクラスを作る必要があります。

しかし、無名クラスを使えば、「実装クラスの定義」を省略して、あたかもインターフェースを直接インスタンス化したかのように記述できます。

無名クラスの基本的な書き方と構文

無名クラスの構文は少し特殊ですが、慣れれば非常に便利です。
基本的な書き方を見ていきましょう。

基本構文

// 親クラスまたはインターフェース型の変数 = new 親クラスまたはインターフェース() {
//     ここにメソッドのオーバーライドなどを記述
// };

重要なのは、new の後ろに指定するのは「新しく作るクラス名」ではなく、「継承元のクラス名」または「実装するインターフェース名」であるという点です。

具体例:インターフェースを実装する場合

例えば、Greeting というインターフェースがあるとします。

interface Greeting {
    void sayHello();
}

これを通常の方法(クラス定義)で実装すると以下のようになります。

// 通常の方法:実装クラスを定義する
class EnglishGreeting implements Greeting {
    @Override
    public void sayHello() {
        System.out.println("Hello!");
    }
}

// メイン処理
Greeting greet = new EnglishGreeting();
greet.sayHello();

これを無名クラスを使って書くと、以下のように1つにまとまります。

// 無名クラスを使った方法
Greeting greet = new Greeting() { // ここでクラス定義とインスタンス化を同時に行う
    @Override
    public void sayHello() {
        System.out.println("Hello! (Anonymous)");
    }
};

greet.sayHello();

EnglishGreeting というクラス名を作ることなく、その場で Greeting インターフェースの実装オブジェクトを作成できました。

実践!無名クラスを使ったサンプルコード

実務でよく見かけるパターンとして、スレッド処理やリストのソート処理での使用例を紹介します。

Runnableインターフェースの実装(スレッド処理)

別スレッドで処理を実行する Thread クラスは、引数に Runnable インターフェースの実装を求めます。

ここでも無名クラスが活躍します。

public class ThreadSample {
    public static void main(String[] args) {
        // 無名クラスでRunnableを実装
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("別スレッドで処理を実行中...");
            }
        });

        thread.start();
        System.out.println("メインスレッドの処理");
    }
}

実行結果は以下の通りです。

メインスレッドの処理
別スレッドで処理を実行中...

(※実行順序はタイミングにより前後することがあります)

Listのソート(Comparator)の実装

リストを特定のルールで並べ替えたい場合、Collections.sort メソッドの引数に Comparator インターフェースを渡します。

このような「ちょっとしたロジック」を渡す際にも無名クラスは最適です。

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

public class SortSample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(3);
        numbers.add(1);
        numbers.add(5);

        // 無名クラスで降順(大きい順)ソートのルールを定義
        Collections.sort(numbers, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1; // 降順
            }
        });

        System.out.println(numbers);
    }
}

実行結果は以下の通りです。

[5, 3, 1]

無名クラスとラムダ式の関係と書き換え

Java 8で導入された「ラムダ式」は、実はこの無名クラスの記述をさらに簡略化するために生まれました。

現在では、関数型インターフェース(メソッドが1つだけのインターフェース)を実装する場合は、無名クラスよりもラムダ式を使うのが一般的です。

無名クラスからラムダ式への変換

先ほどの Runnable の例をラムダ式に書き換えてみましょう。

無名クラスの場合

new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
}

ラムダ式の場合

() -> System.out.println("Hello")

驚くほど短くなりました。IDE(EclipseやIntelliJ IDEA)を使っていると、「無名クラスをラムダ式に変換しますか?」と提案されることも多いでしょう。

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

「じゃあ全部ラムダ式でいいの?」というと、そうではありません。

以下のような違いがあります。

  1. 対象の制約
    • ラムダ式:「関数型インターフェース(抽象メソッドが1つ)」でしか使えません。
    • 無名クラス:抽象メソッドが複数あるインターフェースや、通常のクラス(抽象クラス含む)の継承にも使えます。
  2. this の意味(スコープ)
    • ラムダ式this は、そのラムダ式を囲んでいる外側のクラスのインスタンスを指します。
    • 無名クラスthis は、無名クラス自身のインスタンスを指します。

無名クラスの注意点と制約

無名クラスを使う上で、いくつか注意すべきルールがあります。

コンストラクタは定義できない

無名クラスには「クラス名」がないため、明示的なコンストラクタ(クラス名と同じメソッド)を定義することができません

初期化処理が必要な場合は、インスタンスイニシャライザ{ ... } ブロック)を使用する必要があります。

new ArrayList<String>() {
    { // インスタンスイニシャライザ
        add("A");
        add("B");
    }
};

外部の変数は実質的にfinalである必要がある

無名クラスの中から、その外側にあるメソッドのローカル変数を参照する場合、その変数は final(定数) または実質的final(一度も変更されていない)でなければなりません。

public void method() {
    int number = 10;
    
    new Runnable() {
        public void run() {
            // number = 20; // コンパイルエラー!再代入はできない
            System.out.println(number); // 参照のみOK
        }
    };
}

無名クラスのメリットと使いどころ

最後に、現代のJava開発における無名クラスのメリットと使いどころをまとめます。

クラスファイルを増やさずに済む

「このボタンをクリックした時だけ動く処理」のような、他で再利用しない小さなクラスのためにファイルを分けるのは管理が大変です。

無名クラスならコードの中に埋め込めるため、ファイル数を節約できます。

コードの可読性が上がる(場合による)

関連する処理が近くにまとまるため、ロジックの流れを追いやすくなります。

ただし、無名クラスの中身が数十行~数百行になる場合は、逆に読みづらくなるため、通常のクラスとして切り出すべきです。

抽象メソッドが複数の場合(ラムダ式の限界)

Android開発のイベントリスナーなど、オーバーライドすべきメソッドが複数あるインターフェースを実装する場合は、ラムダ式が使えないため無名クラスが活躍します。

Javaのスキルを活かして年収を上げる方法

以上、Javaの無名クラス(匿名クラス)について解説してきました。

なお、Javaのスキルがある場合には、「転職して年収をアップさせる」「副業で稼ぐ」といった方法を検討するのがおすすめです。

Javaエンジニアの需要は非常に高いため、転職によって数十万円の年収アップはザラで、100万円以上年収が上がることも珍しくありません。

なお、転職によって年収を上げたい場合は、エンジニア専門の転職エージェントサービスを利用するのが最適です。

今すぐ転職する気がなくとも、とりあえず転職エージェントに無料登録しておくだけで、スカウトが届いたり、思わぬ好待遇の求人情報が送られてきたりするというメリットがあります。

併せて、副業案件を獲得できるエージェントにも登録しておくと、空いている時間を活かして稼げるようなJavaの案件を探しやすくなります。

転職エージェントも副業エージェントも、登録・利用は完全無料なので、どんな求人や副業案件があるのか気になる方は、気軽に利用してみるとよいでしょう。