Java StreamAPIとは?使い方もわかりやすく解説

当ページのリンクには広告が含まれています。
Java StreamAPIとは?使い方もわかりやすく解説

Java StreamAPIは、データの処理をもっとシンプルで効率的に行うための機能です。

この記事では、初心者でも理解できるように、Java StreamAPIの概要からその使い方、注意すべきポイントまで丁寧に解説します。StreamAPIを使いこなすことで、コードがもっと簡潔になり、効率よくデータを操作できるようになります。

この記事を通して、Javaプログラムの実用的なスキルを向上させましょう。また、初心者にとってもわかりやすいサンプルコードを豊富に紹介しながら進めていきます。

本記事の監修者:Zetto
Zetto

現役ITエンジニア
Java Gold保有
TypeScript(Vue.js)
Java(SpringBoot)
Programming Dream編集長


目次

Java StreamAPIの概要

Java StreamAPIとは?

Java StreamAPIは、Java 8で追加された機能の一つで、大量のデータを簡潔なコードで処理できるようにするための仕組みです。StreamAPIを使うことで、データを順次操作するためのループや条件分岐をシンプルに記述することが可能です。

Java StreamAPIの特徴

Java StreamAPIの主な特徴は「ストリーム」という概念を用いることです。ストリームとは、データの流れを扱う手段で、リストや配列などのデータを順番に操作することができます。ストリームを使うことで、たとえば「リスト内の偶数をすべて取得する」「全ての要素を2倍にする」といった処理を、わかりやすく記述することが可能です。

StreamAPIが導入された背景

StreamAPIが導入された背景には、従来のJavaでのデータ操作が多くのコードを必要とし、わかりづらいものになっていたという問題があります。特にデータのフィルタリングや変換のような操作では、従来の方法では何行にもわたるループや条件分岐が必要でした。しかし、StreamAPIを使うことで、こうした操作を数行で簡潔に書くことができます。これにより、コードの可読性が向上し、ミスの発生も減少します。

StreamAPIのメリット

コードが簡潔になる理由

StreamAPIを使うと、データの操作を1行で書けることが多くなります。たとえば、「リストの中の数字をすべて足し合わせる」処理では、従来のループで行うと複数行のコードが必要でしたが、StreamAPIを使うと1行で表現できます。このようにコードがシンプルになるため、プログラム全体の見通しが良くなります。

大量データの処理が効率的になる

StreamAPIは大量のデータを扱う際にもその真価を発揮します。並列処理という仕組みを使えば、複数のプロセッサを活用して一度に多くのデータを処理することが可能です。これにより、処理速度が向上し、大量のデータを効率的に操作することができます。

StreamAPIの基礎知識

ストリームとは?

ストリームは「データの流れ」を指す概念です。リストや配列などのコレクションから生成されるストリームを使って、フィルタリングや変換、集計などの操作を連続的に行います。ストリームは一方向に流れるので、データを破壊的に操作せず、簡潔に処理することが可能です。

Java StreamAPIの基本的な使い方

StreamAPIの使い方の流れ

StreamAPIを使う基本的な流れは次の通りです。

  1. Streamの生成: Streamは、リストや配列などから生成します。
  2. 中間操作: Streamに対して変換やフィルタリングなどの操作を行います。
  3. 終端操作: 最後に、データを集めたり、表示したりします。

Streamの生成方法

Streamの生成はとても簡単です。リストや配列からStreamを作ることができます。例えば、List<String>からStreamを生成するには、以下のようにします。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Stream<String> nameStream = names.stream();

このようにして、データをストリームに変換します。

Streamの種類

順次ストリームと並列ストリーム

  • 順次ストリーム: データを1つずつ順番に処理します。
  • 並列ストリーム: 複数のデータを同時に処理し、パフォーマンスを向上させます。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> sequentialStream = numbers.stream();
Stream<Integer> parallelStream = numbers.parallelStream();

中間操作と終端操作の詳しい解説

中間操作と終端操作の違い

Streamには「中間操作」と「終端操作」があります。中間操作は、データのフィルタリングや変換を行う操作で、たとえばfiltermapなどがあります。終端操作は、ストリームの処理結果を集めたり、表示したりする操作で、collectforEachなどがあります。

中間操作の種類

フィルタリング(filter)の使い方

filterは、条件に合った要素だけを取り出す中間操作です。例えば、「リストの中から偶数だけを選ぶ」ような使い方ができます。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = numbers.stream()
                                   .filter(n -> n % 2 == 0)
                                   .collect(Collectors.toList());

このコードでは、n % 2 == 0の条件を満たす要素だけが新しいリストに集められます。

マッピング(map)の使い方

mapは、データを変換するために使います。例えば、「すべての要素を2倍にする」という操作を行うことができます。

List<Integer> numbers = Arrays.asList(1, 2, 3);
List<Integer> doubledNumbers = numbers.stream()
                                      .map(n -> n * 2)
                                      .collect(Collectors.toList());

このコードでは、リストの各要素を2倍にして新しいリストを作ります。

平坦化(flatMap)の使い方

flatMapは、リストの中にリストがある場合に、そのリストをフラット(平坦)にする操作です。

List<List<String>> namesNested = Arrays.asList(
    Arrays.asList("Alice", "Bob"),
    Arrays.asList("Charlie", "David")
);
List<String> flatList = namesNested.stream()
                                   .flatMap(List::stream)
                                   .collect(Collectors.toList());

このコードでは、ネストされたリストを一つのリストに変換しています。

終端操作の種類

コレクター(collect)の使い方

collectは、Streamの結果をリストやセットに集めるための終端操作です。例えば、フィルタリングした結果をリストとしてまとめる場合に使います。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> filteredNames = names.stream()
                                  .filter(name -> name.startsWith("A"))
                                  .collect(Collectors.toList());

このコードでは、Aから始まる名前だけを集めています。

まとめ操作(forEach)の使い方

forEachは、ストリームの各要素に対して何らかの処理を行うために使います。たとえば、リスト内のすべての要素を表示する場合に便利です。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream().forEach(name -> System.out.println(name));

このコードでは、リスト内のすべての名前を一つずつ表示します。

短絡操作(anyMatch, allMatch, noneMatch

短絡操作を使うことで、ストリーム内の要素が特定の条件を満たしているかどうかを確認できます。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0);

このコードは、「リストの中に偶数があるか」を確認しています。

Java StreamAPIを使った具体例

フィルタとマップを組み合わせた例

数字のリストから偶数のみを抽出する

次に、フィルタとマップを組み合わせた例を見てみましょう。たとえば、リストの中から偶数だけを選び、その後で各偶数を2倍にする処理です。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> processedNumbers = numbers.stream()
                                        .filter(n -> n % 2 == 0)
                                        .map(n -> n * 2)
                                        .collect(Collectors.toList());

名前リストの大文字変換

次の例では、名前のリストをすべて大文字に変換します。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> upperCaseNames = names.stream()
                                   .map(String::toUpperCase)
                                   .collect(Collectors.toList());

このコードでは、mapを使って各名前を大文字に変換し、新しいリストにまとめています。

StreamAPIでデータ集計

リストの合計値を求める

StreamAPIを使ってリストの合計値を求めることも簡単です。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
                 .mapToInt(Integer::intValue)
                 .sum();

このコードでは、mapToIntを使ってリスト内の数字を合計しています。

最大値と最小値を求める

StreamAPIを使ってリスト内の最大値と最小値を求めることも可能です。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
OptionalInt max = numbers.stream()
                         .mapToInt(Integer::intValue)
                         .max();
OptionalInt min = numbers.stream()
                         .mapToInt(Integer::intValue)
                         .min();

グループ化したデータの集計

StreamAPIはデータのグループ化も得意です。たとえば、名前のリストを頭文字でグループ化することができます。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
Map<Character, List<String>> groupedNames = names.stream()
                                                 .collect(Collectors.groupingBy(name -> name.charAt(0)));

このコードでは、各名前をその頭文字でグループに分けています。

よくあるJava StreamAPIのつまずきポイント

NullPointerExceptionに注意!

StreamAPIを使う際、Null値が含まれているとNullPointerExceptionが発生することがあります。特に注意すべき点は、リスト内にNullがある場合です。

Nullを含むリストの対処法

Nullを含むリストを操作する場合、filterを使ってNullを取り除くのが一般的です。

List<String> names = Arrays.asList("Alice", null, "Charlie");
List<String> filteredNames = names.stream()
                                  .filter(Objects::nonNull)
                                  .collect(Collectors.toList());

このコードでは、Null値を取り除いたリストを作っています。

ストリームの再利用はできない?

Streamは一度しか使えない理由

Streamは、一度使うと再利用することができません。たとえば、同じストリームに対して複数回操作を行おうとすると、エラーが発生します。

再利用するための対策

もしストリームを再利用したい場合は、新しいストリームを生成する必要があります。たとえば、リストから新たにストリームを作成することで同じ処理を繰り返すことが可能です。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Stream<String> nameStream = names.stream();

Java StreamAPIの応用

並列処理でパフォーマンスを向上させる

並列ストリーム(parallelStream)の使い方

StreamAPIでは、並列ストリームを使うことでデータの処理を複数のスレッドで同時に行うことができます。これにより、処理が早くなります。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.parallelStream()
       .forEach(n -> System.out.println(n));

このコードは、並列に各要素を処理します。

並列処理のメリットと注意点

並列処理を使うと速度が向上しますが、順番が保証されない点に注意が必要です。特に順序が重要な場合には、通常のストリームを使った方が良いでしょう。

カスタムコレクターを作る

コレクターの基本

StreamAPIのcollectは既成のコレクターを使うだけでなく、独自のコレクターを作ることもできます。

独自のロジックを持ったコレクターの作成方法

例えば、特定の条件を満たす要素のみを集めるカスタムコレクターを作ることができます。詳細な方法は少し複雑ですが、独自のロジックを持つ処理を簡単に集約できます。

Java StreamAPIを使ったベストプラクティス

StreamAPIの使いすぎに注意

ストリームを使うべきケースと使わないべきケース

StreamAPIは便利ですが、すべてのケースで使うべきではありません。特に、小さなリストや簡単な処理には通常のループの方がわかりやすいこともあります。過剰に使うと逆に可読性が低下することがあります。

可読性を重視したStreamAPIの活用

複雑な処理を簡単にするためのコツ

StreamAPIを使う際は、コードの可読性を意識することが重要です。例えば、あまりに多くの中間操作を連鎖させると、理解が難しくなります。適宜メソッドに分けることで、コードをシンプルに保つことができます。

Java StreamAPIのまとめ

Java StreamAPIでできることとその魅力

Java StreamAPIは、データ操作を簡単かつ効率的にする強力なツールです。フィルタリング、変換、集計といった操作を簡潔に書けるため、Javaプログラミングの可能性を広げてくれます。

初心者向けの次のステップ

StreamAPIを理解したら、次は他のJavaの便利な機能も学んでみましょう。特に、OptionalLambda式など、Java 8で追加された他の機能もStreamAPIと組み合わせることで、より強力なプログラムが作れるようになります。また、公式のドキュメントや関連書籍も参考にしながら、ぜひ実際に手を動かして試してみてください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次