読み込み中...

Java匿名クラスプログラミングマスターへの9ステップ!

Javaプログラミングの匿名クラスを理解するための詳細なガイド Java
この記事は約30分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Javaの匿名クラスは、名前を付けずに一度だけ使う実装をその場で作れる構文です。イベント処理、スレッド起動、抽象クラスの一時的な拡張などで使われ、初心者がオブジェクト指向プログラミングの実感をつかむ材料にもなります。

ただし、匿名クラスは構文がやや詰まって見えるため、newinterface@Overriderun()の関係を分けて読む必要があります。その関係を押さえると、ラムダ式との違いや注意点、詳細な使い方、カスタマイズの判断も整理できるのが基本です。

公式ドキュメントによれば、匿名クラスは式として宣言とインスタンス化を同時に行う仕組みです。詳しい仕様はOracle Java Tutorials: Anonymous ClassesJava Language Specification 15.9.5が一次情報になります。

動作確認環境
  • Java 21 LTS / OpenJDK 21
  • 標準API中心、Swing例では java.desktop モジュールを利用
📖 この記事で学べること
  • 匿名クラスの構文と通常クラス、ラムダ式との違い
  • RunnableActionListener、抽象クラスでの使い分け
  • コンパイルエラーやランタイムエラーの読み解き方
  • メモリリークや可読性に関する注意点
  • カスタマイズと応用例を含むサンプルコードの読み方

Javaとは

Javaは、クラスを中心にプログラムを組み立てるオブジェクト指向プログラミング言語です。ソースコードはjavac.classファイルへ変換され、JVM上で実行されるため、OSごとの差を吸収しやすい設計になっています。

この仕組みにより、業務アプリケーション、Android関連の開発、サーバーサイド、学習用のプログラミングまで広い領域で使われます。初心者にとっては、classpublicstaticvoidmainなどの語が最初の壁になりやすいです。

一般に、Javaではデータと処理をクラスにまとめ、必要に応じてextendsで継承し、implementsでインターフェイスを実装するのが目安です。その延長線上に匿名クラスがあり、短い実装をその場で作れる構文として理解できます。

Javaの基礎型やコレクションに不安がある場合は、Java List型完全ガイドのような配列・リスト系の解説と合わせて読むと理解がつながります。匿名クラスもオブジェクトを渡す場面が多いため、型の見方が土台になるのがポイントです。

プログラミング言語としての特徴

Javaの特徴は、型安全、例外処理、ガベージコレクション、豊富な標準APIにあります。StringListMapThreadRunnableなどの型を組み合わせることで、処理の役割を明確にしながらプログラミングできます。

その型安全性により、存在しないメソッド呼び出しや戻り値の不一致はコンパイル時に検出されますし、ここがポイントです。一方、NullPointerExceptionのように実行時まで表面化しない問題もあるため、初心者はコンパイルエラーとランタイムエラーを分けて読む必要があります。

これらの特徴は、匿名クラスの読み方にも影響します。匿名クラスは名前がないだけで型を持つオブジェクトなので、代入先や引数の型を見れば、どのメソッドを実装すべきか判断できるのが一般的です。

匿名クラスの役割

匿名クラスの役割は、特定のインターフェイスや抽象クラスに対して、一時的な実装をその場で与えることです。名前付きクラスを別ファイルに作るほどではない処理で、new Runnable() { ... }のように宣言と生成を同時に行います。

このとき、newの後ろに書かれるRunnableは、実際にはインスタンス化されるクラス名ではなく、実装対象のインターフェイス名です。波括弧{}の内部にrun()などのメソッドを実装することで、無名の具象クラスが作られます。

new Thread(new Runnable() {
    public void run() {
        System.out.println("匿名クラスを使用してスレッドを作成しました");
    }
}).start();

結果: 期待される出力は「匿名クラスを使用してスレッドを作成しました」です。Threadに渡されたRunnablerun()が別スレッドで呼ばれる構造になるのが現実的です。

ただし、スレッドの実行順序は環境や処理内容に左右されます。複数の出力が絡むサンプルコードでは、順番まで固定されるとは限らない点を押さえてください。

💡 Tips: 匿名クラスは「クラス名がない」だけで、オブジェクトとして生成されます。変数へ代入したり、メソッド引数として渡したりできる点は通常のインスタンスと同じです。

匿名クラスの作り方

匿名クラスの作り方は、対象の型を決め、new 型名()に続けて波括弧内へ実装を書く流れになると整理できます。Javaの文法としては、インスタンス生成式の中でクラス本体を定義していると考えると理解しやすくなります。

具体的には、インターフェイスなら未実装メソッドをすべて実装し、抽象クラスなら抽象メソッドを埋めます。通常クラスを拡張する場合は、必要なメソッドを@Overrideで上書きし、処理を差し替える形になると理解できます。

初心者がつまずきやすいのは、末尾の;です。匿名クラスはクラス宣言ではなく式なので、代入文やメソッド呼び出しの一部として扱われ、閉じ波括弧の後ろにセミコロンが必要になる場面があります。

基本的な構造

基本形はnew インターフェイス名() { メソッド実装 }、またはnew 抽象クラス名() { メソッド実装 }です。引数付きコンストラクタを持つ通常クラスを拡張する場合は、new Base(arg) { ... }のように親クラスのコンストラクタへ値を渡します。

この構造では、匿名クラス自体に明示的なコンストラクタ名を付けられません。クラス名が存在しないため、初期化が必要な場合はインスタンス初期化ブロック{ ... }や、親クラスのコンストラクタ引数を使う設計になると覚えるとよいでしょう。

一方、単一抽象メソッドのインターフェイスであれば、Java 8以降はラムダ式に置き換えられる場合があります。RunnableComparatorのような関数型インターフェイスでは、匿名クラスとラムダ式の両方を比較して読むと違いが見えます。

観点匿名クラスで見る要素初心者が確認する点注意点関連する語
生成newと波括弧を組み合わせます式として扱われるか確認します末尾の;を忘れやすいです匿名クラス
対象interfaceや抽象クラスを使います実装対象の型を読みます複数の型を同時に継承できませんimplements
実装@Overrideでメソッドを上書きします引数と戻り値を一致させますシグネチャ違いでエラーになります@Override
スレッドRunnableを渡しますrun()の中身を読みます実行順序は固定と限りませんThread
GUIActionListenerを使いますイベント発生時の処理を追います解除忘れに注意しますactionPerformed
ラムダ式単一抽象メソッドなら置換候補です匿名クラスとの差を比べますthisの意味が変わります->
変数参照外側のローカル変数を読めますfinal相当か確認します再代入すると使えませんeffectively final
コンストラクタ匿名クラス名のコンストラクタは書けません親の引数を見る必要があります初期化ブロックで補います{}
可読性短い処理に向きますネストの深さを見ます長くなると読みづらいですリファクタリング
例外メソッド宣言に従いますthrowsの範囲を見ます勝手に検査例外を広げられませんException
アクセス外側のメンバーへアクセスできますprivateメンバーの参照を確認します保持期間が長いとリーク要因になりますスコープ
継承通常クラスも拡張できます上書き対象を見ますfinalクラスは拡張できませんextends
抽象クラス抽象メソッドを埋めます未実装が残らないか確認しますコンパイルエラーになりやすいですabstract
インターフェイス契約だけをその場で満たします必要なメソッド数を見ますメソッドが多いと冗長になりますinterface
コールバック処理を後から呼ばせます呼び出し側のタイミングを見ます副作用を小さくしますcallback
イベントユーザー操作に反応します引数eの型を確認しますUIスレッドを塞がないようにしますActionEvent
比較処理Comparatorをその場で作れます戻り値の符号を見ますラムダ式のほうが短い場合がありますcompare
コレクション並べ替えや絞り込みに使えます対象要素の型を見ますnull混入に注意しますList
スコープ宣言箇所の近くで完結します外側の変数参照を確認します長い処理は分離候補ですローカル変数
保守小さな差し替えに使います変更範囲を読みます重複したら名前付きクラスを検討します保守性
テスト依存差し替えに使えます期待するメソッドだけ実装します複雑なモックは別手段が向きますテストダブル
カスタマイズ既存APIへ処理を差し込みます拡張点を確認します過度な匿名化は避けますカスタマイズ
応用例イベント、スレッド、比較に使います型から用途を判断しますラムダ式との境界を意識します応用例
サンプルコード短い例で構文を確認します省略された型定義を補いますそのままでは動かない雛形もありますサンプルコード
注意点可読性と参照保持を見ます外側オブジェクトの寿命を確認しますリスナー解除が必要な場合があります注意点
詳細な使い方APIの引数型を起点に読みますどのメソッドを実装するか決めます抽象メソッド数を確認します詳細な使い方
詳細なカスタマイズ親クラスの一部処理を差し替えます上書き対象の契約を守ります副作用を閉じ込めます詳細なカスタマイズ
詳細な注意点寿命、例外、可読性を確認しますコードレビュー観点を持ちます短さだけで選ばないようにします詳細な注意点
解説型、実装、生成を分けます波括弧の役割を読みます用語を混同しないようにします解説
プログラミング処理の受け渡しに使います責務が小さいか確認します長い処理は名前付きクラスへ移しますプログラミング

具体的な手順

匿名クラスを書くときは、利用先のAPIが求める型を最初に確認します。new Thread(...)ならRunnableaddActionListener(...)ならActionListenerのように、引数の型から実装対象が決まります。

その次に、対象の型が持つ抽象メソッドを調べますが、これは押さえたい点です。Javaアノテーションの解説でも触れられる@Overrideを付けると、メソッド名や引数の間違いをコンパイル時に見つけやすくなります。

手順1:基本構文

基本構文では、new Runnable()の直後に匿名クラス本体を書きます。Runnableは抽象メソッドrun()を持つため、そのメソッドを波括弧内で実装する必要があると考えられます。

このサンプルコードでは、スレッドに渡す処理を匿名クラスとして定義しています。start()を呼ぶと、Threadが内部でrun()を呼び出す流れになります。

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("新しいスレッドが実行されました");
    }
}).start();

結果: 期待される出力は「新しいスレッドが実行されました」です。@Overrideにより、run()の定義がRunnableの契約と一致しているかをコンパイラが確認すると言えるでしょう。

手順2:インスタンスの生成

匿名クラスは、定義と同時にインスタンスが生成されます。名前付きクラスのようにclass MyRunnableを宣言してからnew MyRunnable()を書く流れではありません。

ただし、次のサンプルコードはSomeInterfaceが別途定義されている前提の雛形です。実際にコンパイルする場合は、someMethod()を持つinterface SomeInterfaceを用意する必要があります。

new SomeInterface() {
    @Override
    public void someMethod() {
        System.out.println("someMethodが呼び出されました");
    }
};

結果: SomeInterfaceが正しく定義され、このオブジェクトのsomeMethod()が呼ばれた場合、期待される出力は「someMethodが呼び出されました」です。単独では呼び出し文がないため、表示は発生しません。

手順3:メソッドの実装

抽象クラスを使う場合も考え方は同じです。abstractメソッドが残っている型はそのまま生成できないため、匿名クラス本体で未実装部分を埋めます。

この例ではAbstractClassという抽象クラスがあり、abstractMethod()を実装する想定です。サンプルコードとして読む場合も、前提となる抽象クラスの定義を補って考える必要があります。

new AbstractClass() {
    @Override
    public void abstractMethod() {
        System.out.println("abstractMethodが実装されました");
    }
};

結果: AbstractClassが定義済みで、この匿名クラスのabstractMethod()が呼ばれた場合、期待される出力は「abstractMethodが実装されました」です。生成だけではメソッドは呼ばれません。

詳細な使い方

詳細な使い方を理解するには、匿名クラスを「一時的な処理の受け渡し」として捉えるのが近道です。JavaのAPIは、処理そのものを引数として受け取りたい場面でRunnableActionListenerComparatorなどの型を要求します。

その要求に対して、匿名クラスは短い実装をその場で渡します。プログラミング初心者が構文だけを暗記すると混乱しやすいので、「どの型の、どのメソッドが、いつ呼ばれるか」を順番に読むと整理できるのが基本です。

具体的には、GUIのクリック処理、別スレッドで実行する処理、並べ替えの比較処理が代表例です。ラムダ式で短く書ける場面でも、匿名クラスの詳細な使い方を知っておくと古いコードや教材の読解に役立ちます。

実用的なテクニック

実用面では、インターフェイスをその場で実装する方法、ラムダ式へ置き換える判断、イベントリスナーとして登録する方法を押さえます。これらは書き方が違っても、呼び出し元に処理を渡すという点で同じ流れになるのが目安です。

一方、匿名クラスの中に多くの処理を書くと、どこまでが外側の処理で、どこからがコールバックなのか分かりにくくなります。長くなった場合は、名前付きクラス、別メソッド、ラムダ式への分割を検討するのが現実的です。

テクニック1:インターフェイスの利用

インターフェイスは、実装すべきメソッドの契約を表す型です。匿名クラスでインターフェイスを使うと、呼び出し側が求める契約をその場で満たせます。

ただし、次のコードは説明用の雛形です。インターフェイス名メソッド名はJavaの識別子として利用できますが、実際の開発では具体的な英数字の型名に置き換えるのが一般的です。

new インターフェイス名() {
    @Override
    public void メソッド名() {
        // メソッドの実装
    }
};

結果: これは匿名クラスの構造を示す雛形です。具体的なインターフェイス定義と呼び出し処理を追加すると、対象メソッドの中に書いた処理が期待される動作になるのがポイントです。

この形では、@Overrideが読み手への合図にもなります。メソッド名の打ち間違いや引数型の不一致を検出しやすいため、サンプルコードでも省略しないほうが扱いやすいです。

テクニック2:ラムダ式の活用

Java 8以降では、抽象メソッドが一つだけのインターフェイスをラムダ式で書けます。匿名クラスより短くなる一方で、thisの参照先やメンバー宣言の可否が異なるため、完全に同じ構文ではありません。

そのため、ラムダ式は「匿名クラスの短縮形」とだけ覚えると誤解が残りますし、これが一つの目安です。FunctionalInterfaceの考え方、キャプチャされるローカル変数、戻り値の推論まで含めて整理すると、使い分けが安定します。

インターフェイス名 オブジェクト名 = (パラメータ) -> {
    // メソッドの実装
};

結果: これはラムダ式の雛形です。対象インターフェイスが単一抽象メソッドを持つ場合、引数に応じて処理が呼ばれる構造になります。

同様に、Javaのオーバーライド解説を読むと、匿名クラスで何を上書きしているのかを追いやすくなるのが一般的です。ラムダ式では@Overrideを書かないため、型推論の結果をIDEで確認すると理解が進みます。

テクニック3:イベントリスナーの設定

イベント駆動のプログラミングでは、ユーザーのクリックや入力に反応する処理をリスナーとして登録します。SwingではaddActionListenerActionListenerを渡し、イベント発生時にactionPerformedが呼ばれますが、覚えておくと役立つでしょう。

この使い方では、匿名クラスの処理が即時に実行されるわけではありません。登録後、ボタンがクリックされたタイミングで呼び出されるため、コードの記述位置と実行タイミングを分けて考える必要があります。

ボタンオブジェクト.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        // イベントハンドラのコード
    }
});

結果: これはイベントリスナー登録の雛形です。具体的なJButtonなどに適用すると、クリック時にactionPerformed内の処理が期待される動作になります。

⚠️ 注意: Swingのイベント処理はイベントディスパッチスレッド上で動くため、重い処理を直接書くと画面応答が悪くなる場合があるのが現実的です。時間のかかる処理は別スレッドやSwingWorkerの利用を検討してください。

詳細な対処法

匿名クラスで起きる問題は、構文エラー、型の不一致、未実装メソッド、外側変数の扱い、実行時のnull参照に分けられます。詳細な対処法では、エラー文の先頭だけでなく、どの型をどこで要求されたのかを読むことが大切になります。

そのため、初心者はjavacやIDEが示す行番号を起点に、直前の括弧、セミコロン、@Override対象を確認すると整理できます。匿名クラスは括弧が深くなりやすいため、フォーマットを整えるだけで原因が見えることもあります。

エラー対処の流れを身につけるには、うるう年判定のような条件分岐中心の記事も参考になります。たとえばJavaでうるう年を判定する解説では、条件を読み分ける練習ができると理解できます。

一般的なトラブルシューティング

一般的なトラブルは、匿名クラスの中だけで完結しているとは限りません。呼び出し元のメソッドがどの型を引数に取るか、インポートが足りているか、対象メソッドが本当に存在するかを確認する必要があります。

たとえばActionListenerを使うなら、java.awt.event.ActionListenerjava.awt.event.ActionEventのインポートが関係します。Runnablejava.lang配下なので明示的なインポートは不要です。

これらの確認を進めると、プログラミングでよくある「匿名クラスの問題に見えるが、実際には型名やインポートの問題だった」という状況を切り分けられますし、ここを基本と考えるとよいでしょう。エラーメッセージは短く要約せず、全文を読む姿勢が必要です。

エラーの解消法

コンパイルエラーでは、メソッド未実装、戻り値の不一致、アクセス修飾子の弱体化がよく見られます。publicで宣言すべきメソッドを省略したり、voidのはずが値を返したりすると、匿名クラス全体が生成できません。

一方、ランタイムエラーでは、匿名クラス内のロジックが原因になる場合があります。null参照、配列の範囲外、外部リソースのクローズ漏れなどは、構文が正しくても発生する問題です。

エラー1:コンパイルエラーの対処

コンパイルエラーの対処では、最小のサンプルコードへ切り出す方法が有効です。問題が匿名クラス本体にあるのか、呼び出し側にあるのかを分けると、修正箇所を絞れますし、ここがポイントです。

このサンプルコードは、public class Exampleの中にmainを置き、Runnable匿名クラスをThreadへ渡しています。括弧の対応、String[]@Overrideの位置を確認してください。

public class Example {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名クラスの例");
            }
        }).start();
    }
}

結果: 期待される出力は「匿名クラスの例」です。Runnablerun()を正しく実装しているため、構文としてはコンパイル可能な形になります。

ただし、publicpublcのように誤入力したり、Runnableを未定義の型へ置き換えたりするとコンパイルエラーになります。String[]の角括弧やmainのシグネチャも確認対象です。

エラー2:ランタイムエラーの対処

ランタイムエラーは、コンパイルを通過した後に発生すると覚えるとよいでしょう。匿名クラスに限らず、nullの値へメソッドを呼ぶとNullPointerExceptionが発生する可能性があります。

次のサンプルコードは、文字列変数にnullを入れたままlength()を呼びます。エラー例として扱うコードなので、期待される出力ではなく、例外発生の構造を読むためのサンプルコードです。

public class Example {
    public static void main(String[] args) {
        String str = null;
        System.out.println(str.length());
    }
}

結果: 期待される動作は、str.length()の位置でNullPointerExceptionが発生することです。nullにはlength()を呼び出せないためです。

この問題は、呼び出し前にnullかどうかを分岐すれば避けられます。エスケープ処理のように文字列を扱う処理が増える場合は、Javaエスケープ処理の解説も合わせて確認すると安全な文字列処理の視点が増えます。

public class Example {
    public static void main(String[] args) {
        String str = null;
        if (str != null) {
            System.out.println(str.length());
        } else {
            System.out.println("文字列がnullです");
        }
    }
}

結果: 期待される出力は「文字列がnullです」です。ifnullを判定しているため、length()の呼び出しを避ける流れになります。

ℹ️ 補足: Java 14以降では、状況によってNullPointerExceptionのメッセージが詳しく表示されます。ただし、例外メッセージだけに頼らず、値がnullになった経路を追うことが大切です。

詳細な注意点

詳細な注意点として押さえたいのは、可読性、外側オブジェクトの参照、ローカル変数の制約、イベントリスナーの解除です。匿名クラスは短く書ける反面、寿命の長いオブジェクトへ登録すると参照関係が見えにくくなると考えられます。

そのため、匿名クラスを使う前に、処理が短いか、再利用されないか、外側の状態に強く依存しないかを確認します。特にGUIやサーバー処理では、リスナーやコールバックの保持期間が長くなる場合があります。

初心者が詳細な注意点を学ぶ際は、構文の短さだけで判断しないことが大切です。匿名クラス、ラムダ式、名前付きクラスのどれが読みやすいかは、処理の長さと責務の範囲で変わりますが、これは押さえたい点です。

安全なコーディングのためのヒント

安全に書くには、匿名クラスの中で扱う変数を最小限にします。外側のローカル変数を参照する場合、その変数はfinalまたは実質的にfinalである必要があります。

この制約は、後から値が変わるローカル変数を匿名クラスが保持すると、読み手にとって状態が追いにくくなるためです。Javaのコンパイラは再代入されたローカル変数のキャプチャを許可しません。

ただし、フィールドや配列、ミュータブルなオブジェクトの中身は変更できる場合があると言えるでしょう。その自由度は便利さではなくリスクにもなるため、匿名クラス内の副作用は小さく保つほうが扱いやすいです。

パフォーマンスへの影響

匿名クラスはオブジェクトを生成するため、非常に高頻度で作り続ける処理では生成コストを意識します。ただし、一般的なアプリケーションでは、可読性や責務分離の判断が先に来ることが多いと考えられます。

一方、イベントリスナーのように一度登録して長く使う処理では、生成コストより参照保持のほうが問題になりやすいです。不要になったリスナーを解除しないと、外側オブジェクトが解放されにくくなる場合があるのが基本です。

これらの注意点は、詳細な使い方とセットで理解する必要があります。短いサンプルコードでは問題が見えにくいため、実際のプログラミングではオブジェクトの寿命も読み取る意識が求められます。

注意点1:メモリリークの予防

メモリリークの予防では、匿名クラスが外側のオブジェクトを暗黙に参照する可能性を考えますし、これが一つの目安です。内部クラスの一種として扱われるため、外側インスタンスへアクセスする設計では参照関係が残ります。

  • イベントリスナーを登録した場合、不要になったタイミングで解除できるAPIがあるか確認します。
  • ファイルやデータベース接続などの外部リソースは、try-with-resourcesや明示的なclose()で寿命を管理するのが目安です。
  • 匿名クラス内から外側の大きなオブジェクトを参照し続けないよう、必要な値だけを渡す設計にします。

こうした確認をすると、匿名クラスのカスタマイズが増えても参照関係を追いやすくなります。特に長寿命のリスナーでは、登録と解除を対で考えることが大切です。

注意点2:処理速度の最適化

処理速度を考える場面では、匿名クラスを毎回生成する必要があるかを確認するのがポイントです。同じ処理を何度も使うなら、private static finalのフィールドや名前付きクラスに分けるほうが読みやすい場合があります。

  • 同じ処理を繰り返し渡す場合は、インスタンス再利用やメソッド参照を検討します。
  • 単一抽象メソッドの処理なら、ラムダ式やComparator.comparingのような標準APIが使えるか確認するのが一般的です。
  • 重い処理は匿名クラスの中に直接詰め込まず、別メソッドへ分けて責務を見える形にします。

ただし、最適化だけを先行するとコードの意図が読み取りにくくなります。匿名クラスを使う目的が一時的な差し替えなのか、再利用可能な部品化なのかを分けると判断しやすくなるのが現実的です。

詳細なカスタマイズ

詳細なカスタマイズでは、匿名クラスを使って既存クラスの一部動作を差し替えます。JavaのAPIは、抽象メソッドやイベントリスナーを通じて拡張点を提供しているため、その拡張点に短い処理を差し込む形になります。

その設計は、小さな差分を局所化できる一方で、処理が増えると読みにくくなると整理できます。カスタマイズ内容が複数箇所で再利用されるなら、匿名クラスではなく名前付きクラスや専用メソッドに切り出すほうが保守しやすいです。

拡張性の高い設計

拡張性の高い設計では、変わりやすい処理をインターフェイスとして受け取り、呼び出し側で具体処理を渡します。匿名クラスは、この「処理を渡す」場面で小さな実装を作る手段になります。

たとえば並べ替え条件、入力検証、イベント発生時の処理は、アプリケーションの要件によって変わりやすい部分です。こうした箇所にComparatorや独自インターフェイスを使うと、呼び出し元を大きく変更せずに振る舞いを切り替えられますが、覚えておくと役立つでしょう。

一方、匿名クラスを多用しすぎると、クラス名による説明が失われます。処理の意味が長いコメントなしに伝わらない場合は、名前付きクラスやメソッド名で意図を表すほうが自然です。

一歩進んだ技法

一歩進んだ技法として、匿名クラス内で必要なメソッドだけを上書きし、既存のAPIへ最小限の変更を与える方法があります。SwingのイベントリスナーやThreadの拡張は、その代表的な応用例です。

このサンプルコードでは、ボタンにActionListenerを登録していると理解できます。実際に動かすには、buttonJButtonなどのインスタンスとして用意され、必要なインポートがある状態を前提にします。

button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        // ボタンがクリックされた時の処理をここに記述
        System.out.println("ボタンがクリックされました");
    }
});

結果: ボタンがクリックされた場合、期待される出力は「ボタンがクリックされました」です。登録時点では出力されず、イベント発生時にactionPerformedが呼ばれます。

カスタマイズ1:継承とオーバーライド

継承を使ったカスタマイズでは、既存クラスのメソッドを匿名クラス内で上書きすると覚えるとよいでしょう。Threadを直接拡張する書き方は学習例として分かりやすいですが、処理の分離という点ではRunnableを渡す形もよく使われます。

この例では、Threadrun()を匿名クラスで上書きしています。.start()が匿名クラスのインスタンスに対して呼ばれる点を確認してください。

new Thread() {
    @Override
    public void run() {
        // ここに処理を記述
        System.out.println("スレッドが実行されました");
    }
}.start();

結果: 期待される出力は「スレッドが実行されました」です。Threadを匿名クラスとして拡張し、run()の処理を差し替えるサンプルコードになると考えられます。

ただし、業務コードではスレッド生成を直接扱うより、ExecutorServiceなどの抽象化を使う設計もあります。匿名クラスの構文を理解したうえで、APIの選択は用途に合わせて検討します。

カスタマイズ2:機能の追加

機能の追加では、既存の処理に小さな振る舞いを差し込みますし、ここを基本と考えるとよいでしょう。匿名クラスは名前を持たないため、外部から追加メソッドを直接呼び出す用途には向きません。

そのため、呼び出し側が知っている型のメソッド、たとえばRunnableならrun()へ処理を入れます。匿名クラスの中に独自メソッドを書いても、代入先の型からは通常呼び出せない点が注意点です。

new Thread(new Runnable() {
    @Override
    public void run() {
        // ここに処理を記述
        System.out.println("新しいスレッドが実行されました");
    }
}).start();

結果: 期待される出力は「新しいスレッドが実行されました」です。Runnableとして渡した匿名クラスのrun()が、Threadから呼ばれます。

⚠️ 注意: 匿名クラスに独自メソッドを追加しても、参照型がインターフェイスの場合はそのメソッドへ直接アクセスできません。外部から使う必要がある処理は、インターフェイス側に定義するか名前付きクラスへ分けてください。

応用例とサンプルコード

応用例とサンプルコードでは、匿名クラスをイベント処理、スレッド処理、データ処理の差し替えに使う流れを確認すると言えるでしょう。Javaのプログラミングでは、処理をオブジェクトとして渡す場面が多く、匿名クラスはその理解に役立つ題材です。

ただし、現代のJavaではラムダ式やメソッド参照で短く書ける場面も増えています。匿名クラスは古い書き方として切り捨てるのではなく、既存コードを読むための基礎、ラムダ式との比較対象、特別な上書きが必要な場面の選択肢として扱うと整理できます。

実務での活用法

実務での活用法としては、GUIのイベントリスナー、テスト用の小さな差し替え、スレッドへ渡す短い処理が挙げられますし、ここがポイントです。どの応用例でも、匿名クラスが担当する処理を短く保つことが読みやすさにつながります。

  1. GUIアプリケーションでは、クリックや入力に反応するActionListenerをその場で定義できます。
  2. データ処理では、条件判定や比較処理を小さな部品として渡せますが、これは押さえたい点です。
  3. スレッド処理では、Runnableを使って別スレッドで実行する処理を記述できます。

その応用例の中でも、スレッド処理は構文を理解しやすい題材です。次のサンプルコードは、Runnableを匿名クラスで実装し、Threadへ渡しています。

new Thread(new Runnable() {
    @Override
    public void run() {
        // ここにスレッドで実行する処理を記述
        System.out.println("新しいスレッドが実行されました");
    }
}).start();

結果: 期待される出力は「新しいスレッドが実行されました」です。start()によって新しいスレッドが開始され、run()内の処理が呼ばれる構造です。

このサンプルコードの主役は、Runnableを実装した匿名クラスです。クラス名を付けずに一度だけ使う処理を渡しているため、短い非同期処理の説明に向いています。

独自の実装アイデア

独自の実装アイデアとしては、入力値の検証、並べ替え条件の切り替え、ボタンクリック時の処理差し替えなどがあります。いずれも、呼び出し側のAPIが何らかのインターフェイスを受け取る形にしておくと、匿名クラスで振る舞いを渡せますし、これが一つの目安です。

  1. データ処理では、条件判定の実装を匿名クラスとして渡し、処理の分岐を外から変えられます。
  2. GUIアプリケーションでは、画面部品ごとにイベントハンドリングを分けて書けます。
  3. 学習用のプログラミングでは、インターフェイスと実装の関係を短いコードで確認できるのが基本です。

この例では、ボタンのクリックイベントを匿名クラスで処理します。ActionEventにはイベントの発生元やコマンド文字列などが含まれるため、必要に応じてe.getSource()e.getActionCommand()を参照できます。

button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        // ボタンがクリックされたときの処理をここに記述
        System.out.println("ボタンがクリックされました");
    }
});

結果: ボタンがクリックされた場合、期待される出力は「ボタンがクリックされました」です。buttonの定義、画面表示、イベント処理の準備が整っていることが前提になるのが目安です。

実際の応用例では、匿名クラス内の処理が長くなった時点で設計を見直します。画面更新、入力検証、外部通信を一つのactionPerformedへ詰め込むと、テストと保守が難しくなります。

まとめ

Javaの匿名クラスは、名前を付けずに一度だけ使う実装を作る構文です。new Runnable() { ... }のように、生成と実装を同時に書けるため、イベント処理やコールバックの解説で頻繁に登場するのがポイントです。

その一方で、匿名クラスは括弧が深くなりやすく、外側の変数やオブジェクトを参照する性質もあります。詳細な注意点を踏まえ、短い処理に限定する、長くなったら名前付きクラスへ分ける、ラムダ式と比較する、という判断が必要になります。

初心者は、匿名クラスを「特殊な省略記法」と考えるより、インターフェイスや抽象クラスの実装をその場で作る仕組みとして読むと理解しやすいです。@OverrideinterfaceabstractThreadActionListenerの関係を追うと、サンプルコードの意味がつながりますが、覚えておくと役立つでしょう。

詳細な使い方、詳細なカスタマイズ、応用例を通じて、Javaプログラミングでは型を起点に処理を渡す考え方が見えてきます。匿名クラスはラムダ式に置き換わる場面もありますが、既存コードの読解やオブジェクト指向の学習では今も理解しておきたい構文です。

プログラミング学習で迷った場合は、サンプルコードをただ写すのではなく、対象の型、実装するメソッド、呼び出されるタイミングを順番に確認してください。その読み方を身につけると、匿名クラスだけでなく、Javaのコールバック設計全体を扱いやすくなります。

関連記事

著者: Japanシーモア編集部

Japanシーモアは、Web/IoT/APP/SYS 分野のプログラミング情報を体系的に提供するメディアです。本記事は編集部による執筆とAI支援を組み合わせて制作し、公開前に編集部が校正しています。誤りや改善案がございましたらお問い合わせよりご連絡ください。

※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。