中間操作と終端操作を組み合わせる
中間操作により取り出した要素に対して操作を実行したストリームを生成し、終端操作により中間操作を終えたストリームに対する最終操作を実行するのが一連の流れとなる。終端操作をしたストリームに関しては、新たに中間操作をしたりすることはできない。 今回の記事では下記のようなことを確認しようと思う。
- ケース1中間操作2つ、終端操作1つの実装
- ケース2collectの有効活用ケース
- ケース3toArrayの有効活用ケース
- ケース4reduceの有効活用ケース
- ケース5findAnyとfindFirstの使い分けケース
実装してみる
これまでの中間操作の実装確認と終端操作の実装確認と同様に、確認には「3, 2, 1, 1, 2, 3, 4」のInteger型のリストをstreamにしたもの対して行う。
ケース1
中間操作filter(3未満の値を抜き出し)と中間操作map(全値を10倍)して、終端操作forEach(値をディスプレイ表示)するように繋げた。
- 「3, 2, 1, 1, 2, 3, 4」の3未満の値を抜き出し⇒「2, 1, 1, 2」
- 「2, 1, 1, 2」の値を全て10倍⇒「20, 10, 10, 20」
- 「20, 10, 10, 20」の値をディスプレイ表示
public class Main { public static void main(String[] args) { List<Integer> inputList = List.of(3,2,1,1,2,3,4); Stream<Integer> inputStream = inputList.stream(); test1(inputStream); } static void test1(Stream<Integer> stream){ stream.filter(i -> i < 3) // 中間操作 .map(i -> i*10) // 中間操作 .forEach(i -> System.out.println(i)); // 終端操作 } }
実行結果
20
10
10
20
ケース2
中間操作map(全値を10倍)して、終端操作collect(List→Set型への変換)するように繋げた。
- 「3, 2, 1, 1, 2, 3, 4」の値を全て10倍⇒「30, 20, 10, 10, 20, 30, 40」
- 「30, 20, 10, 10, 20, 30, 40」の値をSet型に変換
- 重複が取り除かれたSet型データが返却され、「20, 40, 10, 30」が表示される。
public class Main { public static void main(String[] args) { List<Integer> inputList = List.of(3,2,1,1,2,3,4); Stream<Integer> inputStream = inputList.stream(); test2(inputStream); } static void test2(Stream<Integer> stream){ Set<Integer> set = stream .map(i -> i*10) // 中間操作 .collect(Collectors.toSet()); // 終端操作 for(Integer i:set){ System.out.println(i); } } }
実行結果
20
40
10
30
ケース3
中間操作distinct(重複削除)して、中間操作sorted(ソート)して、中間操作map("x個"のString型に変換)して、終端操作toArray(List
- 「3, 2, 1, 1, 2, 3, 4」の重複削除⇒「3, 2, 1, 4」
- 「3, 2, 1, 4」をソート⇒「1, 2, 3, 4」
- 「1, 2, 3, 4」に"個"を繋げる⇒「1個, 2個, 3個, 4個」
- 「1個, 2個, 3個, 4個」のString型Listに変換し表示。
public class Main { public static void main(String[] args) { List<Integer> inputList = List.of(3,2,1,1,2,3,4); Stream<Integer> inputStream = inputList.stream(); test3(inputStream); } static void test3(Stream<Integer> stream){ String[] srtLList = stream .distinct() // 中間操作 .sorted() // 中間操作 .map(i -> i+"個") // 中間操作 .toArray(String[]::new); // 終端操作 for(String str:srtLList){ System.out.println(str); } } }
実行結果
1個
2個
3個
4個
ケース4
終端操作reduce(合計値算出)のみを実施した。実施する処理については、BinaryOperatorを利用している。
- 「3, 2, 1, 1, 2, 3, 4」の3と2を取り出して合算(=5)。
- 1の計算結果(=5)+「3, 2, 1, 1, 2, 3, 4」の3つ目を加算(=6)
- 2の計算結果(=6)+「3, 2, 1, 1, 2, 3, 4」の4つ目を加算(=7)
- 全ての値を加算するまで繰り返す
public class Main { public static void main(String[] args) { List<Integer> inputList = List.of(3,2,1,1,2,3,4); Stream<Integer> inputStream = inputList.stream(); test4(inputStream); } static void test4(Stream<Integer> stream){ final BinaryOperator<Integer> operation = (num1, num2) -> { return num1 + num2; }; Optional<Integer> sum = stream .reduce(operation); // 終端操作 System.out.println("合計値:" + (sum.isPresent()? sum.get() :"中身なし")); } }
実行結果
合計値:16
ケース5
findFirstとfindAnyはそれぞれ条件に合致するデータを取り出してくるが、同じ条件だった場合は同じ値を取ってくるようになっている。前記事で確認済み。 ただし、並列処理を行うparallelの条件下においては、findAnyの値が異なってくる。
public class Main { public static void main(String[] args) { List<Integer> inputList = List.of(3,2,1,1,2,3,4); Stream<Integer> inputStream = inputList.stream(); test4(inputStream); } static void test5(Stream<Integer> stream1, Stream<Integer> stream2){ Optional<Integer> result1 = stream1 .parallel() .filter(i -> i>=1) .findFirst(); Optional<Integer> result2 = stream2 .parallel() .filter(i -> i>=1) .findAny(); System.out.println("findFirst:"+(result1.isPresent()? result1.get() :"中身なし")); System.out.println("findAny :"+(result2.isPresent()? result2.get() :"中身なし")); } }
実行結果
findFirst:3
findAny :4
まとめ
それぞれのメソッドの機能を覚えるだけならば簡単であるが、実際に組み合わせた場合などの挙動については実際に動かしてみないと分からないことも多い。特にfindAnyとfindFirstの違いなどは一見するとないので動かしてみないと実感が沸かないと思う。また、reduceの挙動も説明を見ただけではイメージがしにくいものだと思う。そこら辺の理解を深めるのは、やはり動かしてみるのが一番ではないだろうか。