Java Gold試験で避けて通れないのが「ラムダ式とStream API」の分野です。出題数も多く、ここをしっかり押さえておくかどうかで合否が分かれると言っても大げさではありません。
この記事では、基礎から丁寧に解説しつつ、試験で問われやすいポイントに焦点を当てて整理していきます。
ラムダ式の基本を押さえる
ラムダ式は、関数を短く書くための構文です。Java 8で導入され、コードの可読性を大きく向上させました。
基本の書き方
java
(引数) -> { 処理 }
たとえば「受け取った数値を2倍にする」処理は、次のように書けます。
java
(x) -> x * 2
従来の書き方との比較
ラムダ式が登場する前は、匿名クラスを使って書く必要がありました。
匿名クラスを使った場合
java
Runnable r = new Runnable() { @Override public void run() { System.out.println("Hello"); }};
ラムダ式を使った場合
java
Runnable r = () -> System.out.println("Hello");
一目瞭然ですね。ラムダ式を使うと、本質的な処理だけが残ってコードがすっきりします。
関数型インターフェースを理解する
ラムダ式はどこでも使えるわけではありません。関数型インターフェースを引数に取る場所でのみ使用できます。
関数型インターフェースの条件
抽象メソッドを1つだけ持つインターフェースのことを指します。@FunctionalInterfaceアノテーションで明示することが推奨されていますが、アノテーションがなくても条件を満たせば関数型インターフェースとして扱われます。
試験で頻出のインターフェース
| カテゴリ | インターフェース | 引数 → 戻り値 | メソッド |
| 判定 | Predicate<T> | T → boolean | test(T t) |
| 変換 | Function<T, R> | T → R | apply(T t) |
| 変換(同型) | UnaryOperator<T> | T → T | apply(T t) |
| 二変数変換 | BiFunction<T, U, R> | T, U → R | apply(T t, U u) |
| 二変数(同型) | BinaryOperator<T> | T, T → T | apply(T t1, T t2) |
| 消費 | Consumer<T> | T → void | accept(T t) |
| 供給 | Supplier<T> | なし → T | get() |
使用例
java
Predicate<Integer> isGreaterThanTen = x -> x > 10;System.out.println(isGreaterThanTen.test(15)); // trueSystem.out.println(isGreaterThanTen.test(5)); // false
BiFunctionの攻略ポイント
1. 型引数は3つ! <T, U, R>
BiFunctionを使いこなす上で一番重要なのは、ジェネリクスの順番です。
- T: 第1引数の型
- U: 第2引数の型
- R: 戻り値(Result)の型
試験の罠: 型引数の順番を入れ替えて「コンパイルエラー」を選ばせる問題がよく出ます。「最後が戻り値!」と覚えましょう。
2. メソッドは apply(T t, U u)
引数を2つ渡して実行します。
Java
// StringとIntegerを受け取って、Stringを返すBiFunction
BiFunction<String, Integer, String> repeat = (str, count) -> str.repeat(count);
System.out.println(repeat.apply("Java", 2)); // 出力:JavaJava
3. andThenはあるけど、composeはない!
ここが一番の「Gold試験ポイント」です。
- andThen: あります。
BiFunctionの結果(1つ)を、次のFunctionに渡せます。 - compose:ありません。
- なぜなら、
composeは「先に実行した結果」を次の処理に渡しますが、BiFunctionは引数が2つ必要なので、前の処理の結果(1つ)だけでは型が合わないからです。
- なぜなら、
ラムダ式の省略ルール(試験で狙われやすい)
ラムダ式には省略できる部分がいくつかあります。試験では「この書き方はコンパイルエラーになるか?」という形式で出題されることが多いので、ルールを正確に把握しておきましょう。
引数の型は省略できる
java
// 省略前(int x) -> x * 2// 省略後(x) -> x * 2
コンパイラが型を推論してくれるため、明示的に書かなくても問題ありません。
引数が1つなら括弧も省略できる
java
// 省略前(x) -> x * 2// 省略後x -> x * 2
ただし、引数が0個または2個以上の場合は括弧が必須です。
処理が1文ならブレースとreturnを省略できる
java
// 省略前x -> { return x * 2; }// 省略後x -> x * 2
ブレースを使う場合はreturnが必要、省略する場合はreturnも書かない。この対応関係を間違えるとコンパイルエラーになります。
Functionの合成:andThen vs compose
Functionインターフェースには、複数の処理を連結するためのデフォルトメソッドが2つ用意されています。試験では「どっちが先に実行されるか」が100%問われます。
1. andThen:順次実行(AのあとにB)
f1.andThen(f2) と書いた場合、f1を実行してからf2を実行します。
直感的で分かりやすい、エンジニアにおなじみの流れです。
Java
Function<Integer, Integer> plus2 = i -> i + 2;
Function<Integer, Integer> times3 = i -> i * 3;
// (10 + 2) * 3 = 36
System.out.println(plus2.andThen(times3).apply(10));
2. compose:逆順実行(BのまえにA)
f1.compose(f2) と書いた場合、引数に渡したf2を先に実行し、その結果をf1に渡します。
数学の合成関数 $f(g(x))$ のイメージに近いため、初見で間違えやすいので注意です。
Java
// (10 * 3) + 2 = 32
System.out.println(plus2.compose(times3).apply(10));
比較表:試験対策のまとめ
ブログに載せる際は、以下の表のように整理すると読者がスクショしやすくなります。
| メソッド | 実行順序 | イメージ |
f1.andThen(f2) | f1 → f2 | 「Aをしてから(and then)Bをする」 |
f1.compose(f2) | f2 → f1 | 「Bで組み立てる(compose)してからAをする」 |
Stream APIの全体像をつかむ
Stream APIは、コレクションのデータを宣言的に処理するための仕組みです。「何をしたいか」を順番に記述していくスタイルで、従来のfor文による手続き的な書き方と比べて意図が伝わりやすくなります。
基本的な構造
java
list.stream() .中間操作() .中間操作() .終端操作();
処理は大きく中間操作と終端操作に分かれます。
中間操作と終端操作の違い
中間操作
データを変換・絞り込みする操作で、何回でも連結できます。
| メソッド | 役割 |
|---|---|
filter() | 条件に合う要素だけを残す |
map() | 各要素を変換する |
sorted() | 要素を並び替える |
distinct() | 重複を除去する |
limit() | 先頭からn個だけ取得する |
終端操作
Streamの処理を実行して結果を返す操作で、1回だけ呼び出せます。
| メソッド | 役割 |
|---|---|
forEach() | 各要素に対して処理を実行 |
collect() | 結果をListやSetなどに変換 |
reduce() | 要素を集約して1つの値にする |
count() | 要素数を返す |
findFirst() | 最初の要素を返す |
実際のコード例
java
List<Integer> numbers = List.of(1, 2, 3, 4, 5);numbers.stream() .filter(x -> x % 2 == 0) // 偶数だけ残す → 2, 4 .map(x -> x * 2) // 2倍にする → 4, 8 .forEach(System.out::println);
実行結果
48
試験で狙われるポイント
ポイント1:遅延評価の仕組み
中間操作はその場では実行されません。終端操作が呼ばれて初めて、中間操作がまとめて実行されます。
java
List<Integer> numbers = List.of(1, 2, 3);numbers.stream() .filter(x -> { System.out.println("filterが呼ばれた: " + x); return x > 1; });// 何も出力されない
終端操作がないため、filterの中の処理は一度も実行されません。試験では「このコードの出力は?」という形式で問われることがあります。
ポイント2:Optionalの扱い
findFirst()やfindAny()はOptionalを返します。値が存在しない可能性があるためです。
java
List<Integer> numbers = List.of(1, 2, 3);Optional<Integer> result = numbers.stream() .filter(x -> x > 10) .findFirst();System.out.println(result.orElse(0)); // 0
orElse()で値がない場合のデフォルト値を指定できます。他にもorElseThrow()やifPresent()など、Optionalの操作方法は一通り押さえておきましょう。
ポイント3:メソッド参照
ラムダ式をさらに短く書ける場合、メソッド参照が使えます。
java
// ラムダ式list.forEach(x -> System.out.println(x));// メソッド参照list.forEach(System.out::println);
メソッド参照には4つのパターンがあります。
| パターン | 例 |
|---|---|
| staticメソッド | Integer::parseInt |
| インスタンスメソッド(特定のオブジェクト) | System.out::println |
| インスタンスメソッド(任意のオブジェクト) | String::length |
| コンストラクタ | ArrayList::new |
ポイント4:並列ストリームの特性
parallelStream()を使うと、処理が並列化されてパフォーマンスが向上する場合があります。ただし、処理順序は保証されません。
java
List<Integer> numbers = List.of(1, 2, 3, 4, 5);numbers.parallelStream() .forEach(System.out::println);// 出力順序は実行ごとに変わる可能性がある
順序を保証したい場合はforEachOrdered()を使いますが、並列処理のメリットは薄れます。
よくある間違いパターン
ブレースを使うのにreturnがない
java
// コンパイルエラーlist.stream() .map(x -> { x * 2 });
ブレースを使う場合はreturnが必須です。
java
// 正しい書き方list.stream() .map(x -> { return x * 2; });// または省略形list.stream() .map(x -> x * 2);
使用済みのStreamを再利用しようとする
java
Stream<Integer> stream = numbers.stream();stream.forEach(System.out::println);stream.forEach(System.out::println); // IllegalStateException
Streamは一度終端操作を実行すると再利用できません。
練習問題
【第1問】ラムダ式の省略ルール
次のラムダ式の記述のうち、コンパイルエラーになるものはどれですか? A. Consumer<String> c = s -> System.out.println(s); B. Consumer<String> c = (s) -> { System.out.println(s); }; C. Consumer<String> c = String s -> System.out.println(s); D. BiConsumer<String, String> c = (s1, s2) -> System.out.println(s1 + s2);
正解:C 引数の型(String)を明示する場合、括弧
()を省略することはできません。正しくは(String s) -> ...と書く必要があります。
【第2問】関数型インターフェースの選択
「引数を1つ受け取り、何らかの処理を行って結果を返さない(void)」役割を持つインターフェースはどれですか? A. Predicate<T> B. Function<T, R> C. Consumer<T> D. Supplier<T>
正解:C 「消費するだけ(値を返さない)」のが
Consumerです。Predicateはbooleanを返し、Functionは変換後の値を返し、Supplierは引数なしで値を生成します。
【第3問】遅延評価の挙動
次のコードを実行したとき、標準出力に表示される内容はどれですか?
Java
List<String> list = List.of("apple", "banana");
list.stream().filter(s -> {
System.out.print(s + " ");
return s.startsWith("a");
});
A. apple B. apple banana C. banana D. 何も表示されない
正解:D Stream APIの中間操作(filterなど)は、終端操作が呼び出されるまで実行されません。このコードには終端操作がないため、filter内の出力処理は一度も走りません。
【第4問】Streamの再利用
次のコードを実行した結果として正しいものはどれですか?
Java
Stream<Integer> s = Stream.of(1, 2, 3);
s.forEach(System.out::print);
long count = s.count();
System.out.print(count);
A. 1233 B. 123 のみ表示される C. IllegalStateException がスローされる D. コンパイルエラー
正解:C ストリームは一度終端操作(forEach)を実行すると「消費済み」となり、再利用できません。2回目の終端操作(count)を呼び出すと例外が発生します。
【第5問】メソッド参照の書き換え
s -> s.length() というラムダ式をメソッド参照で書き換えたものとして正しいものはどれですか? A. String::length() B. String::length C. s::length D. new String()::length
正解:B 「任意のオブジェクトのインスタンスメソッド」を参照する場合、
クラス名::メソッド名の形式で記述します。括弧()は不要です。
【第6問】Optionalの基本
次のコードの出力結果は何ですか?
Java
Optional<String> opt = Optional.ofNullable(null);
System.out.print(opt.orElse("Empty"));
A. null B. Empty C. 何も表示されない D. NullPointerException がスローされる
正解:B
orElseは、Optionalの中身が空(null)の場合に、引数で指定したデフォルト値を返します。
【第7問】中間操作(mapとflatMap)
List<List<Integer>> list = List.of(List.of(1, 2), List.of(3, 4)); を [1, 2, 3, 4] という平坦なストリームに変換したい場合、どの中間操作を使うべきですか? A. map B. filter C. flatMap D. distinct
正解:C 複数のコレクションを1つのストリームに「平坦化」するには
flatMapを使用します。
【第8問】終端操作(reduce)
次のコードの出力結果は何ですか? List<Integer> nums = List.of(1, 2, 3); int sum = nums.stream().reduce(10, (a, b) -> a + b); System.out.print(sum); A. 6 B. 10 C. 16 D. コンパイルエラー
正解:C
reduce(初期値, 演算)です。初期値10に、リスト内の1, 2, 3が順に加算されるため、10 + 1 + 2 + 3 = 16となります。
【第9問】並列ストリームと順序
forEach を parallelStream() で使用した際の説明として正しいものはどれですか? A. 常に昇順で処理される B. 常に降順で処理される C. 処理の順序は保証されない D. forEach は並列ストリームでは使用できない
正解:C 並列ストリームで
forEachを使うと、複数のスレッドで同時に処理されるため、出力順序は実行のたびに変わる可能性があります。
【第10問】ラムダ式と変数
次のコードの ( 1 ) において、コンパイルエラーになる原因はどれですか?
Java
int num = 10;
Runnable r = () -> {
num = 20; // ( 1 )
System.out.println(num);
};
A. 戻り値の型が指定されていないから B. ラムダ式内で外部のローカル変数を書き換えることはできないから C. Runnableは関数型インターフェースではないから D. numがstatic変数ではないから
正解:B ラムダ式内で参照するローカル変数は、**実質的にfinal(effectively final)**である必要があります。値を再代入しようとするとコンパイルエラーになります。
学習のコツ
処理の流れを追う
Streamは上から下へ、左から右へ順番に処理が流れていきます。各操作で何が起きているか、1ステップずつ追いかける習慣をつけましょう。
頭の中でシミュレーションする
試験本番ではIDEは使えません。コードを見て「この時点でデータはこう変わって…」と頭の中で実行結果を組み立てる練習が効果的です。
手を動かして確認する
理解があいまいな部分は、実際にコードを書いて動かしてみるのが一番です。「こう書いたらエラーになるかな?」という仮説を立てて検証していくと、記憶に定着しやすくなります。
まとめ
ラムダ式とStream APIは、Java Gold試験において配点の大きい重要分野です。
押さえておくべきポイント
- ラムダ式の書き方と省略ルール
- 関数型インターフェースの種類と役割
- 中間操作と終端操作の違い
- 遅延評価の仕組み
- Optionalの基本操作
- メソッド参照の4パターン
- 並列ストリームの特性
最初は複雑に感じるかもしれませんが、パターンを覚えてしまえば得点源になる分野です。特にfilter、map、forEachの組み合わせは頻出なので、確実に使いこなせるようにしておきましょう。


コメント