Java 8 Stream

Java 8 新特性Java 8 新機能


Java 8 APIは、Streamと呼ばれる新しい抽象化を追加しました。これにより、宣言的な方法でデータを処理できます。

Streamは、SQL文でデータベースからデータをクエリするのと似た直感的な方法で、Javaコレクションの演算と表現に対する高階抽象化を提供します。

Stream APIはJavaプログラマーの生産性を大幅に向上させ、効率的でクリーンかつ簡潔なコードを書くことを可能にします。

このスタイルでは、処理する要素の集合をストリームとみなし、ストリームはパイプライン内を伝送され、フィルタリング、ソート、集約などの処理をパイプラインのノードで行うことができます。

要素のストリームはパイプライン内で中間操作(intermediate operation)による処理を受け、最終的に終端操作(terminal operation)によって前述の処理結果を得ます。

+--------------------+       +------+   +------+   +---+   +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+       +------+   +------+   +---+   +-------+

上記のフローをJavaコードに変換すると、次のようになります。

List<Integer> transactionsIds = 
widgets.stream()
             .filter(b -> b.getColor() == RED)
             .sorted((x,y) -> x.getWeight() - y.getWeight())
             .mapToInt(Widget::getWeight)
             .sum();

Streamとは?

Stream(ストリーム)は、データソースからの要素のキューであり、集約操作をサポートします。

  • 要素は特定の型のオブジェクトであり、キューを形成します。 JavaのStreamは要素を格納せず、必要に応じて計算します。
  • データソースストリームのソースです。 コレクション、配列、I/Oチャネル、ジェネレータ(generator)などです。
  • 集約操作SQL文のような操作で、例えばfilter、map、reduce、find、match、sortedなどです。

以前のCollection操作とは異なり、Stream操作にはさらに2つの基本的な特徴があります。

  • Pipelining: 中間操作はすべてストリームオブジェクト自体を返します。 これにより、複数の操作を1つのパイプラインに連結でき、流れるようなスタイル(fluent style)になります。 これにより、遅延実行(laziness)や短絡(short-circuiting)などの操作の最適化が可能になります。
  • 内部反復: 以前はコレクションの走査はIteratorまたはFor-Eachを使用して、コレクションの外部で明示的に反復処理を行っていました。これを外部反復と呼びます。 Streamは、ビジターパターン(Visitor)によって実装された内部反復の方法を提供します。

ストリームの生成

Java 8では、コレクションインターフェースにストリームを生成する2つのメソッドがあります。

  • stream()− コレクション用の逐次ストリームを作成します。

  • parallelStream()− コレクション用の並列ストリームを作成します。

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());

forEach

Streamは、ストリーム内の各データを反復処理するための新しいメソッド 'forEach' を提供します。次のコードスニペットは、forEachを使用して10個の乱数を出力します。

Random random = new Random(); random.ints().limit(10).forEach(System.out::println);

map

map メソッドは各要素を対応する結果にマッピングするために使用されます。次のコードスニペットは、mapを使用して要素に対応する平方数を出力します。

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); //対応する平方数を取得 List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());

filter

filter メソッドは、設定した条件で要素をフィルタリングするために使用されます。次のコードスニペットは、filterメソッドを使用して空文字列をフィルタリングします。

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); //空文字列の数を取得 long count = strings.stream().filter(string -> string.isEmpty()).count();

limit

limit メソッドは、指定された数のストリームを取得するために使用されます。次のコードスニペットは、limitメソッドを使用して10件のデータを出力します。

Random random = new Random(); random.ints().limit(10).forEach(System.out::println);

sorted

sorted メソッドはストリームをソートするために使用されます。次のコードスニペットは、sortedメソッドを使用して出力される10個の乱数をソートします。

Random random = new Random(); random.ints().limit(10).sorted().forEach(System.out::println);

並行(parallel)プログラム

parallelStream は、ストリームの並列処理の代替方法です。次の例では、parallelStreamを使用して空文字列の数を出力します。

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); //空文字列の数を取得 long count = strings.parallelStream().filter(string -> string.isEmpty()).count();

逐次実行と並列実行を簡単に切り替えることができます。


Collectors

Collectors クラスは多くの還元操作を実装しています。例えば、ストリームをコレクションに変換したり、要素を集約したりします。 Collectors はリストや文字列を返すために使用できます。

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList()); System.out.println("リストをフィルタリング:" + filtered); String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", ")); System.out.println("文字列を結合:" + mergedString);

統計

さらに、統計結果を生成する一部のコレクターも非常に便利です。これらは主にint、double、longなどの基本型で使用され、以下のような統計結果を生成するために使用できます。

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics(); System.out.println("リスト内の最大数 :" + stats.getMax()); System.out.println("リスト内の最小数 :" + stats.getMin()); System.out.println("すべての数の合計 :" + stats.getSum()); System.out.println("平均値 :" + stats.getAverage());

Stream 完全な例

次のコードを Java8Tester.java ファイルに配置します:

Java8Tester.java ファイル

import java.util.ArrayList; import java.util.Arrays; import java.util.IntSummaryStatistics; import java.util.List; import java.util.Random; import java.util.stream.Collectors; import java.util.Map; public class Java8Tester { public static void main(String args[]){ System.out.println("Java 7 を使用:"); //空文字列のカウント List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); System.out.println("リスト:" +strings); long count = getCountEmptyStringUsingJava7(strings); System.out.println("空文字列の数は:" + count); count = getCountLength3UsingJava7(strings); System.out.println("長さが3の文字列の数は:" + count); //空文字列を削除 List<String> filtered = deleteEmptyStringsUsingJava7(strings); System.out.println("フィルタリング後のリスト:" + filtered); //空文字列を削除し、カンマでそれらを結合します String mergedString = getMergedStringUsingJava7(strings,", "); System.out.println("結合文字列:" + mergedString); List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); //リスト要素の平方数を取得 List<Integer> squaresList = getSquares(numbers); System.out.println("平方数のリスト:" + squaresList); List<Integer> integers = Arrays.asList(1,2,13,4,15,6,17,8,19); System.out.println("リスト:" +integers); System.out.println("リスト内の最大数 :" + getMax(integers)); System.out.println("リスト内の最小数 :" + getMin(integers)); System.out.println("すべての数の合計 :" + getSum(integers)); System.out.println("平均値 :" + getAverage(integers)); System.out.println("乱数:"); //10個の乱数を出力 Random random = new Random(); for(int i=0; i < 10; i++){ System.out.println(random.nextInt()); } System.out.println("Java 8 を使用:"); System.out.println("リスト:" +strings); count = strings.stream().filter(string->string.isEmpty()).count(); System.out.println("空文字列の数は:" + count); count = strings.stream().filter(string -> string.length() == 3).count(); System.out.println("長さが3の文字列の数は:" + count); filtered = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.toList()); System.out.println("フィルタリング後のリスト:" + filtered); mergedString = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.joining(", ")); System.out.println("結合文字列:" + mergedString); squaresList = numbers.stream().map( i ->i*i).distinct().collect(Collectors.toList()); System.out.println("Squares List: " + squaresList); System.out.println("リスト:" +integers); IntSummaryStatistics stats = integers.stream().mapToInt((x) ->x).summaryStatistics(); System.out.println("リスト内の最大数 :" + stats.getMax()); System.out.println("リスト内の最小数 :" + stats.getMin()); System.out.println("すべての数の合計 :" + stats.getSum()); System.out.println("平均値 :" + stats.getAverage()); System.out.println("乱数:"); random.ints().limit(10).sorted().forEach(System.out::println); //並列処理 count = strings.parallelStream().filter(string -> string.isEmpty()).count(); System.out.println("空文字列の数は:" + count); } private static int getCountEmptyStringUsingJava7(List<String> strings){ int count = 0; for(String string: strings){ if(string.isEmpty()){ count++; } } return count; } private static int getCountLength3UsingJava7(List<String> strings){ int count = 0; for(String string: strings){ if(string.length() == 3){ count++; } } return count; } private static List<String> deleteEmptyStringsUsingJava7(List<String> strings){ List<String> filteredList = new ArrayList<String>(); for(String string: strings){ if(!string.isEmpty()){ filteredList.add(string); } } return filteredList; } private static String getMergedStringUsingJava7(List<String> strings, String separator){ StringBuilder stringBuilder = new StringBuilder(); for(String string: strings){ if(!string.isEmpty()){ stringBuilder.append(string); stringBuilder.append(separator); } } String mergedString = stringBuilder.toString(); return mergedString.substring(0, mergedString.length()-2); } private static List<Integer> getSquares(List<Integer> numbers){ List<Integer> squaresList = new ArrayList<Integer>(); for(Integer number: numbers){ Integer square = new Integer(number.intValue() * number.intValue()); if(!squaresList.contains(square)){ squaresList.add(square); } } return squaresList; } private static int getMax(List<Integer> numbers){ int max = numbers.get(0); for(int i=1;i < numbers.size();i++){ Integer number = numbers.get(i); if(number.intValue() > max){ max = number.intValue(); } } return max; } private static int getMin(List<Integer> numbers){ int min = numbers.get(0); for(int i=1;i < numbers.size();i++){ Integer number = numbers.get(i); if(number.intValue() < min){ min = number.intValue(); } } return min; } private static int getSum(List numbers){ int sum = (int)(numbers.get(0)); for(int i=1;i < numbers.size();i++){ sum += (int)numbers.get(i); } return sum; } private static int getAverage(List<Integer> numbers){ return getSum(numbers) / numbers.size(); } }

上記のスクリプトを実行すると、出力結果は次のようになります:

$ javac Java8Tester.java 
$ java Java8Tester
使用 Java 7: 
列表: [abc, , bc, efg, abcd, , jkl]
空字符数量为: 2
字符串长度为 3 的数量为: 3
筛选后的列表: [abc, bc, efg, abcd, jkl]
合并字符串: abc, bc, efg, abcd, jkl
平方数列表: [9, 4, 49, 25]
列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]
列表中最大的数 : 19
列表中最小的数 : 1
所有数之和 : 85
平均数 : 9
随机数: 
-393170844
-963842252
447036679
-1043163142
-881079698
221586850
-1101570113
576190039
-1045184578
1647841045
使用 Java 8: 
列表: [abc, , bc, efg, abcd, , jkl]
空字符串数量为: 2
字符串长度为 3 的数量为: 3
筛选后的列表: [abc, bc, efg, abcd, jkl]
合并字符串: abc, bc, efg, abcd, jkl
Squares List: [9, 4, 49, 25]
列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]
列表中最大的数 : 19
列表中最小的数 : 1
所有数之和 : 85
平均数 : 9.444444444444445
随机数: 
-1743813696
-1301974944
-1299484995
-779981186
136544902
555792023
1243315896
1264920849
1472077135
1706423674
空字符串的数量为: 2

Java 8 新特性Java 8 新機能

その他の拡張