Java 8から導入されたStream APIは、コレクションの処理を劇的にシンプルかつ効率的にする強力な機能です。しかし、「難しそう」「どれを使えばいいか分からない」と感じていませんか? この記事では、Java開発者が日々の業務で「超便利!」と実感するStream APIの主要な16機能を厳選してご紹介します。
本記事を読めば、Stream APIの基本的な使い方から、具体的なコード例、そして「こんな時どうする?」という疑問を解決できる実践的な知識が身につきます。コードの可読性を高め、保守性を向上させたい方はぜひ最後までお読みください。
Stream APIとは?基本的な概念を理解しよう
Stream APIは、コレクション(List, Setなど)や配列の要素に対して、宣言的に処理を記述するためのJava 8の新機能です。これにより、ループ処理(for文など)で記述していた煩雑なコードを、より簡潔かつ機能的な記述スタイルで表現できるようになります。
Stream APIの最大の特長は、「内部イテレーション」と「遅延評価」です。これにより、データソースから要素を効率的に処理し、パフォーマンスの向上にも貢献します。
Streamの3つの要素:データソース、中間操作、終端操作
Stream APIを使った処理は、通常以下の3つの要素で構成されます。
- データソース(Source): Streamの元となるデータ。コレクション、配列、ファイルI/Oなど。
- 中間操作(Intermediate Operations): 0個以上連結できる操作。Streamを別のStreamに変換します。例:
filter(),map(),sorted()。これらは遅延評価されます。 - 終端操作(Terminal Operations): 1つだけ実行できる操作。Streamの処理を開始し、結果を生成します。例:
forEach(),collect(),count()。
エンジニアStream APIを使うと、複数の処理をメソッドチェーンで繋げられるから、コードがすごく読みやすくなりますよ。
デザイナーなるほど!for文の入れ子でゴチャゴチャしがちな部分も、スッキリ書けるってことですね!
【目的別】超便利Stream API 厳選16選
Streamの生成
まずは、Streamを作成する方法から見ていきましょう。
Collection.stream(): コレクションからStreamを生成する最も一般的な方法。Arrays.stream(): 配列からStreamを生成します。Stream.of(): 指定された要素からStreamを生成します。
// CollectionからStreamを生成
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Stream<String> nameStream = names.stream();
// 配列からStreamを生成
String[] cities = {"Tokyo", "Osaka", "Kyoto"};
Stream<String> cityStream = Arrays.stream(cities);
// 個々の要素からStreamを生成
Stream<Integer> numberStream = Stream.of(1, 2, 3, 4, 5);
中間操作(要素のフィルタリング・変換)
Streamの要素に対して、特定の条件で絞り込んだり、別の形に変換したりします。これらは終端操作が呼び出されるまで実行されません。
filter(Predicate<T> predicate): 指定された条件に合致する要素のみをStreamに残します。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0) // 偶数のみフィルタリング
.collect(Collectors.toList());
// 結果: [2, 4, 6, 8, 10]
map(Function<T, R> mapper): Streamの各要素を別の型や値に変換します。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<Integer> nameLengths = names.stream()
.map(String::length) // 各要素の長さを取得
.collect(Collectors.toList());
// 結果: [5, 3, 7]
distinct(): Stream内の重複する要素を除去します。
List<Integer> duplicateNumbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
List<Integer> uniqueNumbers = duplicateNumbers.stream()
.distinct() // 重複を除去
.collect(Collectors.toList());
// 結果: [1, 2, 3, 4, 5]
sorted()/sorted(Comparator<T> comparator): Streamの要素をソートします。
List<String> fruits = Arrays.asList("grape", "apple", "banana");
List<String> sortedFruits = fruits.stream()
.sorted() // 自然順序でソート
.collect(Collectors.toList());
// 結果: [apple, banana, grape]
limit(long maxSize): Streamの要素を指定された数に制限します。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> firstThree = numbers.stream()
.limit(3) // 最初の3つに制限
.collect(Collectors.toList());
// 結果: [1, 2, 3]
skip(long n): Streamの先頭から指定された数の要素をスキップします。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> skippedNumbers = numbers.stream()
.skip(2) // 最初の2つをスキップ
.collect(Collectors.toList());
// 結果: [3, 4, 5]
終端操作(結果の集約・出力)
Streamの処理を実行し、最終的な結果を得るための操作です。一度終端操作が呼ばれると、そのStreamは消費され、再利用できません。
forEach(Consumer<T> action): Streamの各要素に対してアクションを実行します。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.forEach(System.out::println); // 各要素を出力
// 出力:
// Alice
// Bob
// Charlie
collect(Collector<T, A, R> collector): Streamの要素をコレクションやMap、単一の値などに集約します。非常に多様な用途があります。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Set<Integer> numberSet = numbers.stream()
.collect(Collectors.toSet()); // Setに集約
// 結果: [1, 2, 3, 4, 5] (Setなので順序は保証されない)
reduce(BinaryOperator<T> accumulator)/reduce(T identity, BinaryOperator<T> accumulator): Streamの要素を結合して1つの結果を生成します。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> sum = numbers.stream()
.reduce(Integer::sum); // 全要素の合計を計算
// 結果: Optional[15]
Integer total = numbers.stream()
.reduce(0, (a, b) -> a + b); // 初期値0で合計
// 結果: 15
count(): Stream内の要素の数を返します。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
long count = names.stream().count(); // 要素数を取得
// 結果: 3
min(Comparator<T> comparator)/max(Comparator<T> comparator): Stream内の最小値または最大値を返します。
List<Integer> numbers = Arrays.asList(10, 5, 20, 15);
Optional<Integer> min = numbers.stream().min(Integer::compare); // 最小値
Optional<Integer> max = numbers.stream().max(Integer::compare); // 最大値
// 結果: Optional[5], Optional[20]
anyMatch(Predicate<T> predicate)/allMatch(Predicate<T> predicate)/noneMatch(Predicate<T> predicate): Streamの要素が特定の条件に合致するかどうかを判定します。
List<Integer> numbers = Arrays.asList(2, 4, 6, 8, 10);
boolean hasOdd = numbers.stream().anyMatch(n -> n % 2 != 0); // 奇数が含まれるか
boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0); // 全て偶数か
boolean noNegative = numbers.stream().noneMatch(n -> n < 0); // 負の数がないか
// 結果: false, true, true
findFirst()/findAny(): Streamの最初の要素、または任意の要素を返します。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Optional<String> first = names.stream().findFirst(); // 最初の要素
// 結果: Optional["Alice"]
Optional<String> any = names.stream().findAny(); // 任意の要素 (並列処理時に有用)
// 結果: Optional["Alice"] (多くの場合、最初の要素になる)
まとめ:Stream APIでJava開発を次のレベルへ
Stream APIは、Javaアプリケーション開発においてコードの簡潔さ、可読性、そしてパフォーマンスを向上させるための不可欠なツールです。今回ご紹介した16のAPIを使いこなすことで、日々の開発業務が格段に効率的になるでしょう。
Stream APIの学習は、Javaエンジニアとしてのスキルアップに直結します。ぜひこの記事で得た知識を活かし、ご自身のプロジェクトで積極的にStream APIを取り入れてみてください。
- Stream APIはJava 8以降のコレクション処理を効率化する。
- データソース、中間操作、終端操作の3つの要素で構成される。
filter()やmap()などの中間操作は遅延評価される。forEach()やcollect()などの終端操作で結果が確定する。- メソッドチェーンで処理を繋ぐことで、コードの可読性が向上する。
- 本記事で紹介した16のAPIを使いこなして、より洗練されたJavaコードを目指そう!