今回は、Java 8で導入された「ラムダ式」と「Stream API」の基本を紹介します。list.stream().filter(...).map(...)のような書き方の意味を整理してみました。
対象読者
-
List・Set・Mapの基本とジェネリクスを理解している方 - for文でコレクションを操作するコードを、
filterやmapのようなメソッドチェーンに書き換えたい方 - ラムダ式の
->という書き方や、「関数型インターフェース」という言葉に馴染みがない方
並列Stream(parallelStream)のパフォーマンスチューニングや、独自の関数型インターフェースを設計する際の詳細なルールまでは扱いません。あくまで基本の書き方と、それぞれが何のためにあるのかという整理に絞っています。
検証環境
Java 25(LTS、2025年9月リリース)を前提にしています。2026年3月にリリースされたJava 26は非LTSでサポート期間が6か月に限定されている1ため、学習用途で環境を作るなら、現行LTSであるJava 25を使うのが無難です。
ラムダ式とは何か
ラムダ式は、1つのメソッドだけを持つインターフェース(関数型インターフェース)の実装を、簡潔に書くための構文です2。「名前のないメソッド」のようなものだと考えると分かりやすいです。
「年齢が3歳以上の動物だけを表示する」という条件を、まず独自のインターフェースと匿名クラスで書いてみます。
interface AnimalCondition {
boolean test(Animal animal);
}
static void printAnimals(List<Animal> animals, AnimalCondition condition) {
for (Animal animal : animals) {
if (condition.test(animal)) {
System.out.println(animal.name);
}
}
}
printAnimals(animals, new AnimalCondition() {
@Override
public boolean test(Animal animal) {
return animal.age >= 3;
}
});
AnimalConditionというインターフェース1つのメソッドを実装するためだけに、匿名クラスとして4行以上のコードを書く必要があります。これをラムダ式で書き換えると、次のようになります。
printAnimals(animals, animal -> animal.age >= 3);
ラムダ式の基本構文は「(仮パラメータ) -> 本体」です2。パラメータが1つの場合は括弧を省略でき、本体が単一の式であればreturnも{}も不要です。Oracleの公式チュートリアルでは、ラムダ式が導入された理由として、単一メソッドのインターフェースに対する匿名クラスの記述が冗長すぎたことと、処理そのものをメソッドの引数として渡せるようにする(関数型プログラミング的な書き方をできるようにする)ことが挙げられています2。
関数型インターフェースとjava.util.function
ラムダ式の代入先になれるインターフェースは、抽象メソッドを1つだけ持つ関数型インターフェースに限られます2。先ほどのAnimalConditionもその一例ですが、実際の開発では毎回自分でインターフェースを定義するのではなく、java.util.functionパッケージにある標準の関数型インターフェースを使うのが一般的です2。
| インターフェース | メソッド | 用途 |
|---|---|---|
Predicate<T> |
boolean test(T t) |
条件を判定する |
Function<T, R> |
R apply(T t) |
値を受け取り、別の値に変換する |
Consumer<T> |
void accept(T t) |
値を受け取り、何らかの処理を行う(戻り値なし) |
Supplier<T> |
T get() |
値を何も受け取らずに供給する |
先ほどのAnimalConditionは、実質的にPredicate<Animal>と同じ形をしています。独自のインターフェースを定義する代わりに、標準のPredicate<Animal>を使うこともできます。
メソッド参照:ラムダ式をさらに短く書く
ラムダ式の本体が、既存のメソッドをそのまま呼び出すだけの場合、メソッド参照という::を使った書き方でさらに簡潔に書けます3。
// ラムダ式
animals.sort((a, b) -> Animal.compareByAge(a, b));
// メソッド参照
animals.sort(Animal::compareByAge);
メソッド参照には次の4種類があります3。
| 種類 | 構文 | 例 |
|---|---|---|
| 静的メソッド参照 | クラス::静的メソッド名 |
Animal::compareByAge |
| 特定オブジェクトのインスタンスメソッド参照 | オブジェクト::メソッド名 |
myAnimal::bark |
| 任意のオブジェクトのインスタンスメソッド参照 | 型::メソッド名 |
String::toUpperCase |
| コンストラクタ参照 | クラス::new |
ArrayList::new |
Stream API:コレクションを集計・加工する
Stream APIは、Java 8で導入された、コレクションに対する集計操作(filter・map・集計など)のための機能です4。for文でコレクションを1件ずつ処理する代わりに、処理の流れをメソッドチェーンとして表現できます。
「年齢が3歳以上の動物の名前だけを一覧にする」という処理を、for文とStream APIそれぞれで書いてみます。
// for文
List<String> names = new ArrayList<>();
for (Animal animal : animals) {
if (animal.age >= 3) {
names.add(animal.name);
}
}
// Stream API
List<String> names = animals.stream()
.filter(animal -> animal.age >= 3)
.map(animal -> animal.name)
.collect(Collectors.toList());
Oracleの公式チュートリアルでは、Stream APIとfor文の違いとして、繰り返し方法が「外部反復(コードが明示的にループを制御する)」から「内部反復(JDK側が最適な繰り返し方法を決める)」に変わる点が説明されています4。この内部反復の仕組みにより、後からstream()をparallelStream()に置き換えるだけで並列処理に切り替えられる、という利点もあります。
Stream APIの処理は、大きく3つの要素からなる「パイプライン」として組み立てます。
-
ソース(source):
animals.stream()のように、コレクションなどからStreamを作る -
中間操作(intermediate operations):
filter・mapのように、Streamを別のStreamに変換する。何度でも連ねられる -
終端操作(terminal operation):
collect・forEach・countのように、最終的な結果を生成する。これが呼ばれるまで、実際の処理は実行されない
先ほどの例で言えば、filterとmapが中間操作、collect(Collectors.toList())が終端操作です。終端操作を呼ぶまでは、実際にはまだ何も処理されていない、という点は最初は意識しづらいポイントかもしれません。
ラムダ式とStream APIの関係
filter・map・forEachといったStream APIのメソッドは、いずれも先ほど紹介したPredicateやFunction、Consumerのような関数型インターフェースを引数として受け取ります。つまりStream APIは、「ラムダ式で処理内容を渡す」という仕組みを、コレクション操作に応用したものだと言えます。ラムダ式そのものの書き方を理解しておくと、Stream APIのメソッドチェーンも自然に読み書きできるようになります。
まとめ
- ラムダ式は、1つのメソッドを持つ関数型インターフェースの実装を、
(仮パラメータ) -> 本体という形で簡潔に書く構文 - 関数型インターフェースには
java.util.functionの標準的なもの(Predicate・Function・Consumer・Supplierなど)があり、独自に定義しなくても多くの場面で使える - メソッド参照(
::)を使うと、既存のメソッドをそのまま渡すラムダ式をさらに簡潔に書ける - Stream APIは、コレクションに対する集計操作をパイプライン(ソース→中間操作→終端操作)として表現する仕組み
-
filter・mapなどのStream APIのメソッドは関数型インターフェースを引数に取るため、ラムダ式と組み合わせて使うのが基本
for文とif文で書いていた処理が、filter・map・collectのメソッドチェーンに置き換えられるようになると、コレクションを扱うコードの見通しがかなり良くなります。ラムダ式の構文そのものに慣れることが、Stream APIを読み書きするための一番の近道だと感じました。
脚注
参考資料
- Lambda Expressions | The Java™ Tutorials (Oracle)
- Method References | The Java™ Tutorials (Oracle)
- Aggregate Operations | The Java™ Tutorials (Oracle)
- Java 26 正式リリース — ラーゲイト株式会社
-
Java 26は2026年3月18日にリリースされた非LTS版で、サポート期間は6か月間(2026年9月まで)。現行LTSはJava 25(2025年9月リリース)。詳細はJava 26 正式リリース — ラーゲイト株式会社を参照。 ↩
-
Lambda Expressions | The Java™ Tutorials (Oracle) ↩ ↩2 ↩3 ↩4 ↩5