Stream APIの核心機能
Java 8で導入されたStream APIは、コレクション処理を宣言的に記述できる革新的な機能です。従来の繰り返し処理とは異なり、パイプライン操作により可読性と生産性を飛躍的に向上させます。ここでは、実務で即座に活用できる高度なテクニックを紹介します。
独自コレクターの開発
標準のtoList()やgroupingBy()では対応しきれない要件の場合、Collectorインターフェースを直接実装することで柔軟な収集ロジックを構築できます。例えば、要素を変換しながら重複を排除する特殊なコレクターの実装例:
import java.util.*;
import java.util.function.*;
import java.util.stream.Collector;
public class CustomCollectorDemo {
public static void main(String[] args) {
List<String> words = Arrays.asList("red", "blue", "red", "green", "blue");
Set<String> uniqueColors = words.stream()
.collect(Collector.of(
() -> new LinkedHashSet<>(),
(set, item) -> set.add(item.toUpperCase()),
(left, right) -> { left.addAll(right); return left; },
Characteristics.UNORDERED
));
System.out.println(uniqueColors); // [RED, BLUE, GREEN]
}
}
並列ストリームの戦略的利用
データ規模が大規模な場合、parallelStream()を活用することで処理時間を短縮できます。ただし、スレッドセーフな操作を保証する必要があります。原子操作を用いた安全な並列処理の例:
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
public class ParallelProcessingDemo {
public static void main(String[] args) {
AtomicInteger counter = new AtomicInteger(0);
long sumOfSquares = IntStream.rangeClosed(1, 1_000_000)
.parallel()
.map(n -> {
counter.incrementAndGet();
return n * n;
})
.sum();
System.out.println("合計: " + sumOfSquares);
System.out.println("処理要素数: " + counter.get());
}
}
複雑な集約操作の実装
reduce()を使用してカスタム集約ロジックを構築する方法と、collect()による高度な文字列操作を示します。
階乗計算の例:
import java.util.stream.LongStream;
public class ReduceDemo {
public static void main(String[] args) {
long factorial = LongStream.rangeClosed(1, 10)
.reduce(1, (a, b) -> a * b);
System.out.println("10の階乗: " + factorial); // 3628800
}
}
動的な文字列フォーマット:
import java.util.stream.Collectors;
import java.util.*;
public class AdvancedCollectDemo {
public static void main(String[] args) {
Map<Integer, List<String>> grouped = Arrays.asList("cat", "dog", "bird", "fish")
.stream()
.collect(Collectors.groupingBy(String::length));
String formatted = grouped.entrySet().stream()
.map(entry -> String.format("%d文字:%s", entry.getKey(), entry.getValue()))
.collect(Collectors.joining(" | "));
System.out.println(formatted); // 3文字:[cat, dog, bird] | 4文字:[fish]
}
}
ネスト構造の効率的なフラット化
flatMap()を使って多次元配列や入れ子になったコレクションを単一のストリームに展開する手法:
import java.util.Arrays;
import java.util.stream.IntStream;
public class FlatMapDemo {
public static void main(String[] args) {
int[][] matrix = {
{1, 2, 3},
{4, 5},
{6, 7, 8, 9}
};
int[] flattened = Arrays.stream(matrix)
.flatMapToInt(IntStream::of)
.toArray();
System.out.println(Arrays.toString(flattened)); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
}
}
ショートサーキット操作の活用
大規模データセットでは条件を満たした時点で処理を中断するショートサーキット操作がパフォーマンスに大きく影響します。具体的な適用例:
import java.util.OptionalInt;
import java.util.stream.IntStream;
public class ShortCircuitDemo {
public static void main(String[] args) {
// 最初の負の数を検索
OptionalInt firstNegative = IntStream.rangeClosed(-1000, 1000)
.filter(n -> n < 0)
.findFirst();
firstNegative.ifPresent(n -> System.out.println("最初の負の数: " + n));
// 条件を満たす要素が存在するか判定
boolean hasLargeNumber = IntStream.of(10, 20, 30, 40)
.anyMatch(n -> n > 35);
System.out.println("35より大きい数が含まれる: " + hasLargeNumber); // true
}
}
実践的な適用とパフォーマンス考慮
これらのテクニックを実プロジェクトに導入する際は、必ずベンチマークテストを実施してください。特に並列ストリームはデータサイズ、ソース構造、操作の性質によって効果が大きく変動します。小規模データセットではオーバーヘッドが逆効果になることもあります。また、状態を持つラムダ式は並列処理時に予期しない結果を招くため、副作用のない純粋関数を心がけることが重要です。