カテゴリ: Java 更新日: 2026/04/15

Javaラムダ式と無名クラスの違いを徹底比較!初心者でもわかる使い方ガイド

Javaラムダ式と無名クラスの違いをコード例で比較解説
Javaラムダ式と無名クラスの違いをコード例で比較解説

先生と生徒の会話形式で理解しよう

生徒

「先生、Javaのラムダ式って何ですか?最近コードでよく見かけるんですが、無名クラスとは違うんですか?」

先生

「Javaのラムダ式は、Java 8から導入された機能で、無名クラスをより簡潔に書けるようにしたものです。特に関数型インターフェースを実装する際に便利ですよ。」

生徒

「関数型インターフェースって何ですか?それと、無名クラスとどう違うのか教えてください!」

先生

「それでは、無名クラスとラムダ式の違いを、実際のコード例を見ながら詳しく説明していきましょう!」

1. 無名クラスとラムダ式の基本的な違い

1. 無名クラスとラムダ式の基本的な違い
1. 無名クラスとラムダ式の基本的な違い

Javaの無名クラス(匿名クラス)は、クラス定義と同時にインスタンスを作成する方法です。一方、ラムダ式はより簡潔な記法で同じことを実現できます。無名クラスは抽象クラスやインターフェースを実装する際に使われますが、コードが冗長になりがちです。ラムダ式を使うことで、同じ処理をわずか数行で表現できるようになります。

ラムダ式が使えるのは、関数型インターフェースと呼ばれる、抽象メソッドが1つだけ定義されているインターフェースの場合のみです。例えば、RunnableComparatorなどがこれに該当します。これにより、コードの可読性が向上し、保守性も高まります。

2. 無名クラスを使った従来の書き方

2. 無名クラスを使った従来の書き方
2. 無名クラスを使った従来の書き方

まず、無名クラスを使った従来の書き方を見てみましょう。ここでは、Runnableインターフェースを実装してスレッドを作成する例を紹介します。無名クラスでは、newキーワードの後にインターフェース名を書き、その場でメソッドをオーバーライドします。

このコードでは、runメソッドを実装していますが、わずか1行の処理を書くために多くの記述が必要になっています。無名クラスの場合、クラス定義の構文をすべて書く必要があるため、コードが長くなってしまいます。


public class AnonymousClassExample {
    public static void main(String[] args) {
        // 無名クラスを使った書き方
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("無名クラスで実行しました");
            }
        };
        
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

実行結果は以下のようになります。


無名クラスで実行しました

3. ラムダ式を使った簡潔な書き方

3. ラムダ式を使った簡潔な書き方
3. ラムダ式を使った簡潔な書き方

同じ処理をラムダ式で書くと、驚くほど簡潔になります。ラムダ式の基本的な構文は、(引数) -> { 処理 }という形式です。引数がない場合は空のカッコ()を書きます。処理が1行だけの場合は、波カッコ{}も省略できます。

ラムダ式を使うことで、無名クラスで必要だったnew Runnable()@Overrideといった定型文が不要になります。これにより、コードの意図がより明確になり、読みやすくなります。Java開発において、このような簡潔な記法は生産性の向上につながります。


public class LambdaExample {
    public static void main(String[] args) {
        // ラムダ式を使った書き方
        Runnable runnable = () -> {
            System.out.println("ラムダ式で実行しました");
        };
        
        Thread thread = new Thread(runnable);
        thread.start();
        
        // さらに簡潔に書くこともできる
        Thread thread2 = new Thread(() -> System.out.println("もっと簡潔に実行"));
        thread2.start();
    }
}

実行結果は以下のようになります。


ラムダ式で実行しました
もっと簡潔に実行

4. 引数を持つインターフェースでの比較

4. 引数を持つインターフェースでの比較
4. 引数を持つインターフェースでの比較

次に、引数を持つインターフェースの場合を見てみましょう。ここでは、リストのソートによく使われるComparatorインターフェースを例に挙げます。Comparatorは2つの引数を受け取り、比較結果を返すcompareメソッドを持っています。

無名クラスで書く場合、メソッドのシグネチャをすべて記述する必要がありますが、ラムダ式では引数の型を省略できることが多く、より直感的に書けます。ラムダ式では、コンパイラが型推論を行うため、明示的に型を書く必要がありません。


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

public class ComparatorExample {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("田中");
        names.add("佐藤");
        names.add("鈴木");
        
        // 無名クラスを使った書き方
        Collections.sort(names, new Comparator<String>() {
            @Override
            public int compare(String s1, String s2) {
                return s1.compareTo(s2);
            }
        });
        
        System.out.println("無名クラスでソート: " + names);
        
        // ラムダ式を使った書き方
        names.clear();
        names.add("田中");
        names.add("佐藤");
        names.add("鈴木");
        
        Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
        
        System.out.println("ラムダ式でソート: " + names);
    }
}

実行結果は以下のようになります。


無名クラスでソート: [佐藤, 鈴木, 田中]
ラムダ式でソート: [佐藤, 鈴木, 田中]

5. 関数型インターフェースとは何か

5. 関数型インターフェースとは何か
5. 関数型インターフェースとは何か

ラムダ式を理解する上で重要なのが、関数型インターフェースという概念です。関数型インターフェースとは、抽象メソッドが1つだけ定義されているインターフェースのことを指します。Java 8以降では、@FunctionalInterfaceアノテーションを付けることで、そのインターフェースが関数型インターフェースであることを明示できます。

Javaには標準で多くの関数型インターフェースが用意されています。例えば、java.util.functionパッケージには、FunctionPredicateConsumerSupplierなどがあります。これらを活用することで、より柔軟なプログラミングが可能になります。

Functionインターフェースは入力を受け取って出力を返す処理、Predicateは真偽値を返す条件判定、Consumerは引数を受け取って何かの処理を行う、Supplierは引数なしで値を提供するといった用途で使われます。これらを使いこなすことで、関数型プログラミングのスタイルでコードを書くことができます。

6. 自作の関数型インターフェースを使った例

6. 自作の関数型インターフェースを使った例
6. 自作の関数型インターフェースを使った例

自分で関数型インターフェースを定義することもできます。ここでは、2つの整数を受け取って計算結果を返すシンプルな関数型インターフェースを作成してみましょう。このような独自のインターフェースを定義することで、ビジネスロジックに特化した処理を簡潔に表現できます。

@FunctionalInterfaceアノテーションを付けることで、このインターフェースが関数型インターフェースであることをコンパイラに伝えられます。もし誤って2つ以上の抽象メソッドを定義してしまった場合、コンパイルエラーになるため、バグを未然に防げます。


@FunctionalInterface
interface Calculator {
    int calculate(int a, int b);
}

public class CustomFunctionalInterfaceExample {
    public static void main(String[] args) {
        // 無名クラスで実装
        Calculator addition = new Calculator() {
            @Override
            public int calculate(int a, int b) {
                return a + b;
            }
        };
        
        // ラムダ式で実装
        Calculator subtraction = (a, b) -> a - b;
        Calculator multiplication = (a, b) -> a * b;
        Calculator division = (a, b) -> a / b;
        
        System.out.println("足し算(無名クラス): " + addition.calculate(10, 5));
        System.out.println("引き算(ラムダ式): " + subtraction.calculate(10, 5));
        System.out.println("掛け算(ラムダ式): " + multiplication.calculate(10, 5));
        System.out.println("割り算(ラムダ式): " + division.calculate(10, 5));
    }
}

実行結果は以下のようになります。


足し算(無名クラス): 15
引き算(ラムダ式): 5
掛け算(ラムダ式): 50
割り算(ラムダ式): 2

7. ラムダ式のメリットと特徴

7. ラムダ式のメリットと特徴
7. ラムダ式のメリットと特徴

ラムダ式を使うことには多くのメリットがあります。最も大きなメリットは、コードの簡潔性です。無名クラスでは10行以上必要だった処理が、ラムダ式なら1行で書けることも珍しくありません。これにより、コードの可読性が大幅に向上します。

また、ラムダ式はストリームAPIと組み合わせることで、コレクション操作を非常に効率的に記述できます。例えば、リストの要素をフィルタリングしたり、変換したり、集計したりする処理を、宣言的なスタイルで書けるようになります。これは従来のループ処理に比べて、意図がより明確になります。

さらに、ラムダ式は変数のキャプチャが可能です。外側のスコープで定義された実質的にfinalな変数(値が変更されない変数)を、ラムダ式の中で参照できます。ただし、ラムダ式の中からその変数の値を変更することはできません。この制限により、並行処理での予期しない動作を防ぐことができます。

8. 無名クラスとラムダ式の使い分け

8. 無名クラスとラムダ式の使い分け
8. 無名クラスとラムダ式の使い分け

ラムダ式が便利である一方、無名クラスが適している場面もあります。まず、ラムダ式は関数型インターフェース(抽象メソッドが1つだけのインターフェース)でしか使えません。複数の抽象メソッドを持つインターフェースや抽象クラスを実装する場合は、無名クラスを使う必要があります。

また、無名クラスではthisキーワードが無名クラス自身を指しますが、ラムダ式では外側のクラスを指します。この違いを理解しておくことは重要です。状態を持つ必要がある場合や、インスタンス変数を使いたい場合は、無名クラスの方が適していることがあります。

一般的には、シンプルな処理で関数型インターフェースを実装する場合はラムダ式を、複雑な処理や複数のメソッドが必要な場合は無名クラスを使うという使い分けが推奨されます。プロジェクトのコーディング規約によっても異なりますが、コードの意図が伝わりやすい方を選ぶのが良いでしょう。

9. メソッド参照との関連性

9. メソッド参照との関連性
9. メソッド参照との関連性

ラムダ式をさらに簡潔にする方法として、メソッド参照があります。メソッド参照は、既存のメソッドをラムダ式の代わりに使う記法で、::演算子を使います。例えば、System.out::printlnは、x -> System.out.println(x)と同じ意味になります。

メソッド参照には、静的メソッド参照、インスタンスメソッド参照、コンストラクタ参照などがあります。これらを適切に使うことで、コードをさらに読みやすくできます。特に、既存のメソッドをそのまま渡したい場合は、ラムダ式よりもメソッド参照の方が簡潔で分かりやすくなります。

ただし、メソッド参照は便利な反面、初心者にとっては理解しにくい面もあります。まずはラムダ式に慣れてから、徐々にメソッド参照を取り入れていくのが良いでしょう。コードレビューの際も、チームメンバー全員が理解できる書き方を選ぶことが重要です。

10. 実践的な使用例とパフォーマンスの違い

10. 実践的な使用例とパフォーマンスの違い
10. 実践的な使用例とパフォーマンスの違い

最後に、実践的な使用例を見てみましょう。リストの要素をフィルタリングする処理を、無名クラスとラムダ式の両方で実装してみます。この例では、リストから偶数だけを抽出する処理を行います。

パフォーマンスについては、ラムダ式と無名クラスにほとんど差はありません。Java 8以降のコンパイラは、ラムダ式を効率的にバイトコードに変換するため、実行速度の面では同等と考えて問題ありません。むしろ、コードの保守性や開発効率の観点からラムダ式を選択することが多いでしょう。

ただし、ラムダ式が大量に生成される場合や、メモリ使用量が厳しい環境では、詳細なプロファイリングを行うことをお勧めします。一般的なアプリケーション開発では、このような最適化を気にする必要はほとんどありませんが、高負荷なシステムでは注意が必要です。


import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class FilterExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            numbers.add(i);
        }
        
        // 無名クラスでフィルタリング
        List<Integer> evenNumbers1 = filterNumbers(numbers, new Predicate<Integer>() {
            @Override
            public boolean test(Integer n) {
                return n % 2 == 0;
            }
        });
        
        // ラムダ式でフィルタリング
        List<Integer> evenNumbers2 = filterNumbers(numbers, n -> n % 2 == 0);
        
        System.out.println("無名クラスでフィルタリング: " + evenNumbers1);
        System.out.println("ラムダ式でフィルタリング: " + evenNumbers2);
    }
    
    public static List<Integer> filterNumbers(List<Integer> list, Predicate<Integer> predicate) {
        List<Integer> result = new ArrayList<>();
        for (Integer num : list) {
            if (predicate.test(num)) {
                result.add(num);
            }
        }
        return result;
    }
}

実行結果は以下のようになります。


無名クラスでフィルタリング: [2, 4, 6, 8, 10]
ラムダ式でフィルタリング: [2, 4, 6, 8, 10]

このように、同じ結果を得る場合でも、ラムダ式を使うことでコードが大幅に短くなり、可読性が向上します。Javaプログラミングにおいて、ラムダ式は現代的なコードを書くための必須スキルとなっています。

カテゴリの一覧へ
新着記事
New1
Micronaut
MicronautのDIエラーを解決する方法!NoSuchBeanと循環依存の対処法をやさしく解説
New2
Quarkus
Quarkus REST APIでリクエストパラメータを扱う方法を完全解説!初心者でも理解できる入門ガイド
New3
Java
Javaラムダ式と無名クラスの違いを徹底比較!初心者でもわかる使い方ガイド
New4
Micronaut
MicronautのRequestオブジェクトでリクエスト情報を取得する方法完全ガイド!初心者向けに徹底解説
人気記事
No.1
Java&Spring記事人気No1
Java
Javaの配列とは?基本の使い方・宣言・初期化を初心者向けにわかりやすく解説
No.2
Java&Spring記事人気No2
Java
Javaの配列検索を完全攻略!値の探し方や多次元配列の条件一致を解説
No.3
Java&Spring記事人気No3
Micronaut
Micronautでリクエストを受け取る方法!@Getと@PathVariableの基礎を初心者向けに徹底解説
No.4
Java&Spring記事人気No4
Java
Javaの型変換(キャスト)を徹底解説!暗黙的・明示的変換の違いを整理
No.5
Java&Spring記事人気No5
Micronaut
MicronautのHTTPサーバー性能を最大化!初心者でもできるパフォーマンスチューニング
No.6
Java&Spring記事人気No6
Micronaut
Micronautルーティングのベストプラクティス!整理しやすいURL設計を徹底解説
No.7
Java&Spring記事人気No7
Java
Java ArrayListの使い方完全ガイド|追加・削除・取得・検索の基本操作
No.8
Java&Spring記事人気No8
Quarkus
Quarkusのランタイムモデルを初心者向けに解説!JVMとNativeをわかりやすく理解しよう