Javaのラムダ式は (引数) -> {処理} の形で書く「名前のない関数」です。
Java 8で導入され、forEachやStream API、Comparatorなど日常的なコーディングを大幅に短くできます。
扱うのは、基本構文と省略ルール、実務で頻出する5つの使いどころ、effectively finalなどの注意点です。どれもコード例で確認できます。
「ラムダ式を見かけるが書き方がわからない」という段階からでも、自分で書けるところまで進めます。
具体的には、以下の3つが身につきます。
この記事でわかること
(引数) -> {処理}という書き方の意味が理解できる- forEach・Stream・Comparatorなど、どの場面でラムダ式を使えばよいかわかる
- for文や無名クラスで書いていたコードをラムダ式に書き換えられるようになる
PARTS LIST /目次
Javaのラムダ式とは?
Javaのラムダ式は、一言でいうと「名前のない関数」です。メソッドを定義するとき、通常はアクセス修飾子や戻り値の型、メソッド名を指定します。
しかしラムダ式を使えば、それらを省略して処理内容だけを簡潔に記述できます。
この仕組みは、Java 8で導入されたもので、コードをよりシンプルにし、とくに関数型プログラミングのスタイルをJavaで実現することを目的としています。
ラムダ式の基本的な書き方
ラムダ式の基本形はシンプルです。
(引数) -> {処理内容}
丸かっこ () で引数を定義し、矢印 -> を挟んで、波かっこ {} の中に具体的な処理を記述します。
矢印が引数を処理に渡すイメージで覚えると分かりやすいでしょう。
無名クラスとの違い
ラムダ式が登場する前は、「無名クラス(匿名クラス)」という仕組みを使って似たような処理を実現していました。
しかし、無名クラスは記述が冗長になりがちです。
例えば、JavaFXでボタンがクリックされたときの処理を考えてみましょう。
【無名クラスを使った従来の書き方】button.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("ボタンがクリックされました");
}
});
やりたいことは「メッセージを表示する」だけなのに、周囲の記述が多く読みにくいですね。
同じ処理をラムダ式で書くと、驚くほど簡潔になります。
【ラムダ式を使った書き方】button.setOnAction(event -> System.out.println("ボタンがクリックされました"));
やりたい処理の本質だけが残り、コードがすっきりとしました。定型の記述を省いて処理の本質だけを書けるのが、ラムダ式の大きな力です。
なぜラムダ式で書けるのか?(関数型インターフェース)
「なぜ無名クラスの代わりにラムダ式が使えるの?」と疑問に思った方もいるでしょう。その答えが「関数型インターフェース」です。
関数型インターフェースとは、「やること」が1つだけ決まっているインターフェースのことです(インターフェースを詳しく知らなくても問題ありません)。
先ほどの例で使った EventHandler も、「クリックされたとき何をするか」という処理を1つだけ持つ型です。
「やること」が1つだけなので、Javaは「ラムダ式の中身=そのやること」と自動的に判断できます。だから処理の中身だけを書けばよいのです。
Javaが標準で用意している Runnable や Consumer、BinaryOperator も、すべて関数型インターフェースです。
だからこそ、これらにもラムダ式を代入できます。
標準の関数型インターフェース一覧
Javaの java.util.function パッケージには、よく使う関数型インターフェースがあらかじめ用意されています。
代表的なものを整理しておきましょう。
自分で関数型インターフェースを作ることもできますが、まずはこの6つを覚えておけば、ほとんどの場面で困ることはありません。
ラムダ式が導入された背景(Java 8)
ラムダ式がJava 8(2014年リリース)で導入されたのには、2つの大きな理由があります。
理由
- コードの簡潔化: 先ほどの例で見たように、無名クラスなどの冗長な記述を減らし、よりシンプルで読みやすいコードを書けるようにするため
- 並列処理の効率化: ラムダ式は、同時に導入された「Stream API」と組み合わせて使う。そうすると、コレクション(リストなど)の要素に対する処理を複数のコアで並列実行しやすくなる
Javaのラムダ式の使い方
ラムダ式の便利さがわかったところで、次は具体的な書き方のルールを見ていきます。いくつかの省略ルールを覚えると、さらにコードを短くできます。
LAMBDA / ラムダ式
(a, b)1 -> 2a + b3
↓ 3つの部品に分ける
PARTS / 部品の役割
基本構文と記述ルール
ラムダ式には、状況に応じて記述をさらに簡潔にするためのルールが存在します。
(String s) -> { return s.length(); }(String s) -> s.length()(s) -> s.length()s -> s.length()引数あり・なしの例
ラムダ式は、引数の数に応じて柔軟に記述できます。
【引数がない場合】引数がない場合は、丸かっこ () を省略できません。
// Runnableインターフェースの実装
Runnable runner = () -> System.out.println("タスクを実行します");
runner.run(); // "タスクを実行します" と表示される
【引数が1つの場合】
先ほどのルール通り、丸かっこ () を省略できます。
// 文字列を受け取り、その長さを表示する処理
Consumer<String> consumer = s -> System.out.println(s.length());
consumer.accept("Java"); // 4 と表示される
【引数が2つの場合】
引数が2つ以上ある場合は、丸かっこ () で囲む必要があります。
// 2つの整数を受け取って足し算する処理
BinaryOperator<Integer> operator = (a, b) -> a + b;
int result = operator.apply(10, 20);
System.out.println(result); // 30 と表示される
戻り値を持つ場合の例
処理の結果として値を返す場合は、return キーワードを使います。
// 2つの数値を比較して大きい方を返す
BinaryOperator<Integer> max = (a, b) -> {
if (a > b) {
return a;
} else {
return b;
}
};
ここでも先ほどの省略ルールが役立ちます。処理が1行で完結する場合、波かっこ {} と return を省略可能です。
// 2つの数値を足し算した結果を返す (returnが省略されている)
BinaryOperator<Integer> sum = (a, b) -> a + b;
int result = sum.apply(5, 3); // resultには 8 が入る
Javaのラムダ式の使いどころ
ラムダ式は便利な機能ですが、「どんなときに使うべきか」を理解しておくことが大切です。適切な場面で使うことで、コードの可読性を最大限に高められます。
ラムダ式が活躍するシーン
ラムダ式を使わない方がよいケース
一方で、すべての場面でラムダ式を使えばよいわけではありません。
処理が複雑になる場合や同じ処理を複数箇所で再利用したい場合は、通常のメソッドとして定義した方が可読性や保守性が高くなります。
「3行以内で書けるシンプルな処理」はラムダ式が得意とする領域です。処理が長くなってきたら、通常のメソッドへの切り出しを検討しましょう。
私自身も、Stream APIのチェーン内に10行以上のラムダ式を書いてしまい、レビューで「これはメソッドに切り出して」と指摘された経験があります。
目安として、ラムダ式は3行以内に収めるとチームでの可読性が保たれます。
ラムダ式を禁止する現場がある理由
「ラムダ式は使わないでほしい」と言われる現場もあります。禁止される主な理由は次の3つです。
- チームの習熟度が低い: ラムダ式に慣れていないメンバーが多い場合、コードレビューや保守のコストが上がる
- デバッグが困難になる: 後述するとおり、スタックトレースに名前が出ないため障害調査に時間がかかる
- コーディング規約で統一したい: 従来のfor文や匿名クラスに統一しておけば、書き方のブレを防げる
ただし、Java 8以降の標準APIはラムダ式前提で設計されているため、完全に禁止するとStream APIやOptionalなどの恩恵を受けられなくなります。
「複雑なラムダは禁止し、1行で済むものは許可する」といったチーム内ルールを設けるのが現実的です。
Javaラムダ式の実践コード例
リスト処理(forEach)で使う
リスト(List)の全ての要素に対して同じ処理を行いたい場合、これまでは拡張for文がよく使われてきました。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
for (String name : names) {
System.out.println(name);
}
この処理は、forEach メソッドとラムダ式を使うことで、より直感的に記述できます。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println(name));
「namesリストの各要素(name)を1つずつ表示する」という処理内容が、コードから直接的に読み取れます。
Stream APIと組み合わせる
ラムダ式が最もその真価を発揮するのが、Java 8で導入された Stream API との組み合わせです。
Stream APIを使うと、水の流れのようにコレクションの要素を次々と処理できます。
例えば、「フルーツのリストから、文字数が5文字以上のものだけを抜き出し、大文字に変換して、新しいリストを作る」という処理を考えてみましょう。
【従来の書き方】List<String> fruits = Arrays.asList("apple", "banana", "orange", "grape", "kiwi");
List<String> result = new ArrayList<>();
for (String fruit : fruits) {
if (fruit.length() >= 5) {
result.add(fruit.toUpperCase());
}
}
// result: ["APPLE", "BANANA", "ORANGE", "GRAPE"]
ただし、処理の途中で結果を格納するための新しいリストを用意する記述が増えます。この処理をStream APIとラムダ式で書き換えてみましょう。
【Stream APIとラムダ式を使った書き方】List<String> fruits = Arrays.asList("apple", "banana", "orange", "grape", "kiwi");
List<String> result = fruits.stream()
.filter(f -> f.length() >= 5) // 5文字以上を絞り込む
.map(f -> f.toUpperCase()) // 大文字に変換する
.collect(Collectors.toList()); // 結果をリストにする
// result: ["APPLE", "BANANA", "ORANGE", "GRAPE"]
filter(絞り込み)や map(変換)といった操作がメソッドチェーンで繋がっており、データがどのように処理されていくのかが一目瞭然です。
複雑なデータ処理もラムダ式を使えば宣言的に書けます。
なお、要素数が多い場合は parallelStream() に変えるだけで並列処理に切り替えられます。
ただし、要素数が少ない場合やI/O処理を含む場合は逆に遅くなることもあるので、実測して判断しましょう。
また、Java 16以降では .collect(Collectors.toList()) の代わりに .toList() と書けるようになりました。
まとめセクションのrecord型の例ではこの書き方を使っています。
Comparatorでのソート処理
オブジェクトのリストを特定のルールで並び替えたい場合、Comparator インターフェースを使います。
これも以前は無名クラスで記述するのが一般的でした。
【無名クラスを使ったソート処理】List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b); // 文字列の昇順でソート
}
});
このソート処理も、ラムダ式を使えばたった1行で書けてしまいます。
【ラムダ式を使ったソート処理】List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
Collections.sort(names, (a, b) -> a.compareTo(b));
// さらにメソッド参照を使えばもっと短く書けます
// Collections.sort(names, String::compareTo);
比較のロジック (a, b) -> a.compareTo(b) だけを記述すればよく、簡潔です。
メソッド参照でさらに簡潔に書く
ラムダ式の処理が「既存のメソッドを呼び出すだけ」の場合、::(ダブルコロン)を使ったメソッド参照でさらに短く書けます。
// ラムダ式
names.forEach(name -> System.out.println(name));
// メソッド参照(上と同じ意味)
names.forEach(System.out::println);
先ほどの Comparator の例でコメントに登場した String::compareTo もメソッド参照の一つです。
ラムダ式に慣れてきたら、ぜひメソッド参照も使ってみてください。
Javaラムダ式のメリットと3つの注意点
ラムダ式は強力な機能ですが、良い面だけでなく、いくつか注意すべき点もあります。両方を理解して、適切に使いこなしましょう。
コードの簡素化
ここまでの例で見てきたとおり、コードを短くできることがラムダ式の最大のメリットです。
無名クラスなどの定型的なコードを大幅に削減し、処理の本質に集中したコーディングを可能にします。
コード量が減ることで、ファイル全体の見通しも良くなるでしょう。
可読性・保守性の向上
コードが簡潔になることは、可読性の向上に直結します。
「何をしているのか」がすぐに理解できるコードは、将来の自分や他の開発者にとって保守しやすいコードになります。
とくにStream APIと組み合わせると、処理の流れが一層わかりやすくなります。
デバッグや例外処理の難しさ
その一つがデバッグの難しさです。
ラムダ式は「名前のない関数」です。例外が発生したときのスタックトレース(エラーの発生箇所を示すログ)には自分でつけたメソッド名が表示されないため、エラーの原因特定に時間がかかることがあります。
また、ラムダ式の中からチェック例外(IOExceptionなど、処理が必須の例外)をスローする場合、少し工夫が必要になる点も覚えておいてください。
対処法として、ラムダ式内のロジックをプライベートメソッドに抽出し、メソッド参照で呼び出す方法があります。
こうすればスタックトレースにメソッド名が表示され、デバッグしやすくなります。
外側の変数は変更できない(effectively final)
ラムダ式の中からメソッドのローカル変数を参照することはできますが、その変数の値を変更することはできません。
初期化したあと一度も値を変えていない変数を effectively final(実質的にfinal) と呼びます。
なぜ変更できないのか?ラムダ式は後から別のスレッドで実行される可能性があるためです。
もし変数を変更できると、変更の反映タイミングが予測できずバグの原因になります。
Javaはこの問題を防ぐため、ラムダ式から外側の変数を変更することを禁止しています。
int count = 0;
// コンパイルエラー!ラムダ式の外側のローカル変数は変更できない
Runnable r = () -> count++; // NG
// OK:変更しないなら参照できる
String message = "タスクを実行します";
Runnable r2 = () -> System.out.println(message); // OK
初めてラムダ式を使い始めたときにこのエラーで詰まる方は多いので、事前に知っておくと安心です。
ラムダ式でよくあるエラーと対処法
ラムダ式を書き始めたばかりの頃によく遭遇するエラーをまとめました。
エラーメッセージを読む習慣をつけると、ラムダ式に対する苦手意識は自然と薄れていきます。
Javaラムダ式に関するよくある質問
Q1. ラムダ式と匿名クラスの違いは何ですか?
A1. ラムダ式は関数型インターフェース(抽象メソッドが1つ)専用の簡潔な記法です。匿名クラスは複数メソッドの実装やフィールドを持てる汎用的な記法ですが、記述量が多くなります。単一メソッドの実装だけならラムダ式が推奨されます。
Q2. ラムダ式はどんな場面で使うべきですか?
A2. forEach・Stream・Comparatorなど、関数型インターフェースを引数に取るAPIと組み合わせる場面で使うのが典型的です。単純な繰り返し処理では通常のfor文の方が読みやすい場合もあり、無理にラムダ式化する必要はありません。
Q3. ラムダ式の中で例外はどう扱えばいいですか?
A3. 関数型インターフェースの多くはチェック例外(検査例外)をスローできないため、ラムダ式内でtry-catchするか、実行時例外にラップします。頻出するなら例外処理をラップするユーティリティメソッドを用意すると見通しがよくなります。
まとめ:Javaのラムダ式を使いこなそう
ラムダ式は難しそうに見えて、慣れてしまえばシンプルな仕組みです。今日学んだ基本構文と具体例・注意点が、その第一歩になれば嬉しいです。
初心者がまず練習すべきポイント
もしあなたがラムダ式初心者なら、まずはリストの forEach メソッドで使ってみることをお勧めします。
普段 for ループで書いている処理をラムダ式で書き換える練習を繰り返すことで、その記法に自然と慣れていくでしょう。
慣れてきたら、次は Stream API の filter や map に挑戦してみてください。コレクションの操作が驚くほど簡単で楽しくなるはずです。
ラムダ式と今後のJava開発
Java 8で導入されて以来、ラムダ式はJavaプログラミングの標準的な機能となりました。
2026年現在のJava開発において、ラムダ式やStream APIの知識はもはや必須といっても過言ではありません。
さらに、Java 8以降もラムダ式の活躍の場は広がり続けています。
Java 16(2021年リリース)で導入されたrecord型と組み合わせれば、データの定義から加工までを少ないコードで実現できます。
recordで定義した商品データをStream APIで処理するコードは、次のようになります。
record Product(String name, int price) {}
List<Product> products = List.of(
new Product("りんご", 150),
new Product("バナナ", 200),
new Product("みかん", 100)
);
// 150円以上の商品名だけを取得
List<String> expensiveNames = products.stream()
.filter(p -> p.price() >= 150)
.map(Product::name)
.toList(); // Java 16以降で使える簡潔な書き方。返り値は変更不可のリスト
// 結果: ["りんご", "バナナ"]
さらにJava 21(2023年リリース)では仮想スレッド(Virtual Threads)が正式導入され、Runnable をラムダ式で渡す場面がさらに増えています。
Javaのバージョンが上がるほど、ラムダ式を活かせる場面は増えています。
まずはこの記事で紹介した基本をしっかり身につけ、そこから少しずつ新しい機能との組み合わせにも挑戦してみてください。
| REV. | DATE | 内容 |
|---|---|---|
| 5 | 2026-10-16 | 図解を描き直し(図面のデザインにそろえた) |
| 4 | 2026-10-14 | 雰囲気だけの挿絵を外した |
| 3 | 2026-09-05 | 2026年9月時点の情報で全体を見直し |
| 2 | 2026-07-02 | 2026年7月時点の情報で全体を見直し |
| 1 | 2025-09-27 | 初版 |







