JavaにおけるStream API
メニューを表示するにはスワイプしてください
Java ではデータを処理する方法として、ループ、メソッド、さまざまなアルゴリズムがあります。 しかし、Java 8 で非常に強力なツールが導入されました。それが Stream API です。
簡単に言うと、Stream API は 情報の流れ を 迅速かつ簡単に扱う ための方法です。ここでの情報の流れは コレクションによって表現されます。Stream API にはいくつかの概念があります。主なものを以下に示します。
主な概念
-
Stream: 処理可能なデータ要素のシーケンスを表現;
-
中間操作: 実行後に新しいストリームを生成する操作。例:
filter,map,distinct,sorted; -
終端操作: ストリームの処理を完了し、結果を返す操作。例:
collect,forEach,count,reduce; -
並列ストリーム: データの並列処理を可能にする。
parallel()およびparallelStream()メソッドで並列ストリームを作成。
理論の説明はこれくらいにして、実際にコーディングを始めましょう。
ストリームの宣言は、ストリームに変換したいコレクションでメソッドを使用して行います。
Main.java
12List<String> strings = Arrays.asList("a", "b", "c"); Stream<String> stream = strings.stream();
stream() メソッドを使うことで、文字列のストリームを取得できます。しかし、ストリームを操作するにはラムダ式について理解する必要があります。ストリームのメソッドは主にラムダ式とともに使用されます。
ラムダ式
ラムダ式はJava 8で導入され、Javaにおける匿名関数を簡単に作成する方法を提供します。これまで匿名関数についてはあまり必要性がなかったため扱っていませんでしたが、ここでラムダ式を通じて理解を深めます。
ラムダ式の構文:
Javaにおけるラムダ式の一般的な構文は次のようになります。
Example.java
123(parameters) -> expression // or (parameters) -> { statements; }
-
パラメータ: これはパラメータリストであり、空の場合もあれば、1つ以上のパラメータを含む場合もあります。
-
アロー: 記号
->で表され、パラメータとラムダ式の本体を区切ります。 -
式または文: これは関数の本体であり、式または文のブロックを含みます。
以下は、2つの数値を加算する単純な関数を表すラムダ式の例です:
Example.java
12345678910// Traditional way MathOperation addition = new MathOperation() { @Override public int operate(int a, int b) { return a + b; } }; // Using a lambda expression MathOperation addition = (int a, int b) -> a + b;
上記のコードで実際に何が起こっているのか、そしてラムダ式をどのように使用するかを詳しく見ていきます。
Main.java
1234567891011121314package com.example; // Functional interface with a single abstract method interface MyMathOperation { int operate(int a, int b); } public class Main { public static void main(String[] args) { // Using a lambda expression to implement the interface MyMathOperation addition = (a, b) -> a + b; System.out.println("Sum: " + addition.operate(5, 3)); } }
上記のコードについて:
単一の抽象メソッド MyMathOperation を持つ関数型インターフェース operate を作成。
ラムダ式 を使用してこのメソッドを実装し、2つの数値の加算を実行。
加算結果を出力。
現時点ではこのコードで何が起きているのか理解しづらいかもしれませんが、ここで再びStream APIに戻り、ラムダ式が頻繁に使われる場面を見て、実際にどのように利用するかを確認しましょう。
前回、文字列のリストから文字列のストリームを作成しました。今回は、ストリームメソッドを使って、このストリーム内の各文字列を大文字に変換してみましょう:
Main.java
12345678910111213package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("a", "b", "c"); Stream<String> stream = strings.stream(); stream.map(e -> e.toUpperCase()).toList(); } }
上記のコードでは、ラムダ式と2つのメソッド(map() と toList())を使用しています。toList() メソッドの役割が明確であれば、map() メソッドはストリーム内の各要素を、指定されたラムダ式に従って変換します。
ここでラムダ式がどのように機能するか、詳しく見ていきます。
map() メソッドは、ストリームの各要素に対して toUpperCase() メソッドを適用します。このストリームの要素を e と定義し、ラムダ式を使ってプログラムに各要素にこのメソッドを適用するよう指示しています。
しかし、これはまだ途中段階です。なぜなら、中間操作を適用しただけだからです。つまり、ストリームに対する操作はまだ完了していません。ストリームの処理を完了するには、終端操作を適用する必要があります。これにより、ストリーム上の操作が終了し、特定の値が返されます。例えば、toList() メソッドを使用すると、変更されたストリームがリストに変換されます。
例:
Main.java
1234567891011121314package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("a", "b", "c"); Stream<String> stream = strings.stream(); List<String> list = stream.map(e -> e.toUpperCase()).toList(); System.out.println(list); } }
ストリーム内で利用可能な中間操作について詳しく見ていきます。
中間操作
map() メソッド - このメソッドにはすでに馴染みがあるはずです。ストリーム内の各要素に対して、ラムダ式で指定された操作を実行します。
例えば、文字列のストリーム内の各要素に substring() メソッドを使用します。
Main.java
123456789101112131415package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("Unlock", "Infinity", "with", "Codefinity"); System.out.println("List of strings: " + strings); Stream<String> stream = strings.stream(); List<String> list = stream.map(e -> e.substring(1, 4)).toList(); System.out.println("Modified list: " + list); } }
filter() メソッドは、条件付きのラムダ式を受け取り、その条件に基づいてストリームがフィルタリングされる。つまり、条件を満たすすべての要素がストリームに残り、条件を満たさない要素はストリームから除外される。ここでは、長さが5より大きい要素のみを保持するようにストリームを変更する例を示す。
Main.java
12345678910111213141516package com.example; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("Unlock", "Infinity", "with", "Codefinity"); System.out.println("List of strings: " + strings); Stream<String> stream = strings.stream(); stream = stream.filter(e -> e.length() > 5); List<String> list = stream.map(e -> e.substring(1, 4)).toList(); System.out.println("Modified list: " + list); } }
filter() メソッドを使用して、ストリームから文字列 "with" を削除します。この単語は5文字未満であるためです。
中間操作を複数回連続して使用することも可能です。
例えば、上記のコードを少し簡略化できます:
Main.java
12345678910111213141516package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<String> strings = Arrays.asList("Unlock", "Infinity", "with", "Codefinity"); System.out.println("List of strings: " + strings); List<String> list = strings.stream() .filter(e -> e.length() > 5) .map(e -> e.substring(1, 4)) .toList(); System.out.println("Modified list: " + list); } }
複数のストリームメソッドを連結して使用する場合、各メソッドを新しい行に記述すると、コードの可読性が大幅に向上します。
flatMap() メソッドは、ストリームの各要素を新しいストリームに変換し、結果を1つのストリームに結合します。つまり、このメソッドを使うことで、ストリームを複数のストリームに分割し、それらを1つのストリームにまとめることができます。例えば、各文字列が複数の単語を含む可能性のある文字列リスト(氏名のリストなど)があるとします。そして、これらの各単語の先頭文字を大文字にする必要がある場合:
Main.java
123456789101112131415161718192021222324package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<String> users = Arrays.asList("Ethan Johnson", "Olivia smith", "mason davis", "Ava taylor", "logan brown", "Emma Anderson", "jackson miller"); System.out.println("List of users: " + users); List<String> list = users.stream() .flatMap(e -> Arrays.stream(e.split(" "))) .map(e -> capitalizeFirstLetter(e)) .toList(); System.out.println("List with capitalized names and surnames: " + list); } private static String capitalizeFirstLetter(String word) { if (word == null || word.isEmpty()) { return word; } return Character.toUpperCase(word.charAt(0)) + word.substring(1); } }
上記のコードでは、単語の先頭文字を大文字にする個別のプライベートメソッドを作成し、このメソッドをラムダ式とともに map() メソッド内で使用しています。
flatMap メソッドを使うことで、Arrays.stream(e.split(" ")) メソッドを利用してストリームの各要素を異なるストリームに分割しています。split() メソッドは配列を返すため、この配列をストリームに分割するには Arrays.stream() メソッドを使う必要があります。
その後、これらすべてのストリームが1つのストリームに結合され、作成したメソッドを使用します。これで、ユーザーの名前と姓の先頭文字が大文字になった状態になります。
さらに便利なことを考えてみましょう。
これらの名前と姓を、キーが姓、値が名となる HashMap に格納してみましょう。
これをコードで実装してみます:
Main.java
12345678910111213141516171819202122232425262728293031323334package com.example; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class Main { public static void main(String[] args) { List<String> users = Arrays.asList("Ethan Johnson", "Olivia smith", "mason davis", "Ava taylor", "logan brown", "Emma Anderson", "jackson miller"); System.out.println("List of users: " + users); List<String> list = users.stream() .flatMap(e -> Arrays.stream(e.split(" "))) .map(e -> capitalizeFirstLetter(e)) .toList(); System.out.println("List with capitalized names and surnames: " + list); Map<String, String> usersKeyValue = new HashMap<>(); for (int i = 0; i < list.size() - 1; i+=2) { String name = list.get(i); String surname = list.get(i + 1); usersKeyValue.put(surname, name); } System.out.println("Map with surnames as keys and names as values: " + usersKeyValue); } private static String capitalizeFirstLetter(String word) { if (word == null || word.isEmpty()) { return word; } return Character.toUpperCase(word.charAt(0)) + word.substring(1); } }
単純なループを使用して、名と姓を変数に格納し、その後マップに挿入しました。ループの動作に注目してください。各イテレーションで変数iを2ずつ増加させるのは、すでに記録した姓をスキップする必要があるためです。
distinct()メソッドはストリームから重複を削除します。一般的に、ストリーム内で一意の要素が必要な場合や、リストから重複を素早く排除したい場合に便利です。次の構文で簡単に実現できます:
list.stream().distinct().toList()
-
sortedメソッドはストリーム内のすべての要素を自然順序でソートします。数値では最小から最大、文字列ではアルファベット順に並べ替えます。ソートされたストリームが必要な場合や、リストを素早くソートしたい場合にも有用です。 -
skip(n)メソッドはストリームの最初のn個の要素をスキップします。テキストファイルを扱う際に、最初の n 行が例えばメタデータやファイルの説明である場合などに便利です。また、limit(n)メソッドについても触れておきます。これは一般的にストリーム内の要素数を制限します。たとえば、1000 個の要素を持つストリームを作成しlimit(200)を使用すると、ストリームには最初の 200 個の要素のみが含まれます。
Main.java
123456789101112package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> example = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); example = example.stream().skip(3).limit(5).toList(); System.out.println("List: " + example); } }
ここに挙げるのは主な中間メソッドです。他のメソッドについては、 link から公式Javaドキュメントを参照してください。次に終端メソッドについて説明します。
終端メソッド
-
すでに馴染みのある終端メソッドは
toList()です。これはストリームをリストに変換し、そのリストを返します。つまり、メソッドを適用したストリームを直接リストに代入できます。このメソッドはJava 17で導入され、より複雑な構文であるcollect(Collectors.toList())の代替となります。 -
collect()メソッドもストリームを特定のデータ構造に変換します。パラメータとしてCollectorsインターフェースのメソッドを使用します。このインターフェースにはtoList()、toSet()、toCollection()などのメソッドがあります。例えば:
Main.java
123456789101112131415package com.example; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.stream.Collectors; public class Main { public static void main(String[] args) { List<Integer> example = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Set<Integer> integerSet = example.stream().collect(Collectors.toSet()); System.out.println("List: " + example); System.out.println("Set: " + integerSet); } }
forEach() メソッドはラムダ式を受け取り、ストリーム内の各要素に対して特定の処理を実行。
例:
Main.java
1234567891011package com.example; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> example = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); example.stream().forEach(e -> System.out.println(e + 1)); } }
このメソッドとmapメソッドの違いは、このメソッドが終端処理であるという点です。そのため、このメソッドの後に他のメソッドを呼び出すことはできません。
これらはすべて、ストリームを操作するための基本的なメソッドです。ストリームは複雑なトピックであり、すぐに理解できない場合もあります。しかし、これは実践を通じて習得するテーマです。今後のストリームを使った実践的な章で、リストやデータ配列を便利かつ実用的に操作する方法を十分に体験できます。
1. JavaのStream APIの主な目的は何ですか?
2. 次のうち、Stream APIにおける終端操作はどれですか?
3. Stream APIにおけるmap操作は何を行いますか?
4. Stream APIにおいて、flatMap操作はmapとどのように異なりますか?
5. Stream APIにおけるfilter操作は何を行いますか?
6. Stream APIにおけるforEach操作の目的は何ですか?
7. 次のうち、Stream APIにおける中間操作はどれですか?
8. Stream APIにおいてlimit操作はどのように使用されますか?
フィードバックありがとうございます!
AIに質問する
AIに質問する
何でも質問するか、提案された質問の1つを試してチャットを始めてください