Java8のStream APIと同等の機能を提供するライブラリとして、Lightweight-Stream-API
の紹介エントリを書きましたが、
これ以外にもStream APIと同等の機能を提供するライブラリとしてstreamsupport
というものも存在しています。
Lightweight-Stream-API
と立ち位置的には似ていて、基本的にJava6やJava7環境へStream APIをバックポートするためのライブラリとなっています。
(ちなみに、名前がかぶっていてややこしいですが、Java標準のStreamSupport
クラスのことではありません。)
streamsupportの利用準備
Mavenプロジェクトでstreamsupport
から提供されるStream APIの代替機能を利用する場合、pom.xmlに以下を追記します。
<dependency> <groupId>net.sourceforge.streamsupport</groupId> <artifactId>streamsupport</artifactId> <version>1.5.2</version> </dependency>
また、最終的に生成したモジュールを実行する環境がJava6やJava7である場合は、Retrolambda
の設定も合わせて実施してください。(これによりラムダ式等を組み合わせることができます)
以下の各種サンプルでは基本的にRetrolambda
を組み合わせることを前提にした(ラムダ式やメソッド参照を利用した)コードを記述しています
Java8のStream APIの代わりに使う場合の基本的な使用方法
Java8のStream APIで提供されている操作は、streamsupport
でもほとんど同名の操作として提供されています。
そのためJava8のStream APIでのStream操作は、streamsupport
で置き換えることができます。
例えばStream操作を行う場合のJava8標準とstreamsupport
の対応関係は以下表のようになります
やりたいこと | Java8標準 | streamsupport |
---|---|---|
CollectionからStreamへの変換 | java.util.Collection#stream |
java8.util.stream.StreamSupport#stream |
配列からStreamへの変換 | java.util.Arrays#stream |
java8.util.J8Arrays#stream |
Streamのfilter / map / reduce / sort操作 | java.util.stream.Stream#filter, map, reduce, sorted |
java8.util.stream.Stream#filter, map, reduce, sorted |
StreamからListへの変換 | java.util.stream.Stream#collect |
java8.util.stream.Stream#collect |
基本的に同名の操作で提供されると書きましたが、例えばCollectionからStreamへの変換操作等は、異なる名前の操作で提供されています
以下にJava8標準とstreamsupportを用いた場合の具体的なサンプルコードを比較して載せています。
(基本的にjava8.util.stream
パッケージを利用することで、Java標準のStream APIとほぼ同じコードで実行できることが分かります)
■ListからStreamを生成して操作する
名前(文字列)のListに対して以下の操作をするサンプルです
- 特定の文字列(<unknown>)を除外 (filter)
- 文字列を大文字に変換 (map)
- 辞書順でソート (sorted)
- 各々の要素(文字列)を標準出力に出力する (forEach)
Java8標準の場合
import java.util.Arrays; java.util.List<String> nameList = Arrays.asList("ken", "<unknown>", "taro", "ayumi", "hanako", "jin", "makoto", "<unknown>"); nameList.stream() .filter(s -> !s.equals("<unknown>")) .map(s -> s.toUpperCase()) .sorted() .forEach(System.out::println);
streamsupportの場合
import java.util.Arrays; import java8.util.stream.StreamSupport; List<String> nameList = java.util.Arrays.asList("ken", "<unknown>", "taro", "ayumi", "hanako", "jin", "makoto", "<unknown>"); StreamSupport.stream(nameList) .filter(s -> !s.equals("<unknown>")) .map(s -> s.toUpperCase()) .sorted() .forEach(System.out::println);
実行結果(Java8標準, streamsupport のいずれの場合も同じ)
AYUMI HANAKO JIN KEN MAKOTO TARO
■配列からStreamを生成して操作する
名前(文字列)の配列に対して以下の操作をするサンプルです
- 特定の文字列(<unknown>)を除外 (filter)
- 文字列の文字数に変換(取得) (map)
- 値の大きい順(文字数の多い順)にソート (sorted)
- 重複(文字数が同じ)要素を除外(distinct)
- 結果をListに格納 (collect)
Java8標準の場合
import java.util.Arrays; import java.util.stream.Collectors; String[] srcArray = new String[] {"ken", "<unknown>", "taro", "ayumi", "hanako", "jin", "makoto", "<unknown>"}; java.util.List<Integer> destList = Arrays.stream(srcArray) .filter(s -> !s.equals("<unknown>")) .map(s -> s.length()) .sorted((x, y) -> y - x) .distinct() .collect(Collectors.toList()); System.out.println("destList: " + destList);
streamsupportの場合
import java8.util.J8Arrays; import java8.util.stream.Collectors; String[] srcArray = new String[] {"ken", "<unknown>", "taro", "ayumi", "hanako", "jin", "makoto", "<unknown>"}; java.util.List<Integer> destList = J8Arrays.stream(srcArray) .filter(s -> !s.equals("<unknown>")) .map(s -> s.length()) .sorted((x, y) -> y - x) .distinct() .collect(Collectors.toList()); System.out.println("destList: " + destList);
実行結果(Java8標準, streamsupport のいずれの場合も同じ)
destList: [6, 5, 4, 3]
■IntStreamを操作する
1~10の値範囲のIntStreamを生成してその中の偶数の合計値を計算すサンプルです
Java8標準の場合
import java.util.stream.IntStream; int sum = IntStream.rangeClosed(1, 10) .filter(i -> i % 2 == 0) .sum(); System.out.println("sum: " + sum);
streamsupportの場合
import java8.util.stream.IntStreams; int sum = IntStreams.rangeClosed(1, 10) .filter(i -> i % 2 == 0) .sum(); System.out.println("sum: " + sum);
実行結果(Java8標準, streamsupport のいずれの場合も同じ)
sum: 30
■Streamを並列処理する
streamsupport
は、Java標準のStream APIと同様に並列処理をサポートしています。
(ちなみに、Lightweight-Stream-API
の場合はこの機能を提供していません)
Java8標準の場合
import java.util.Arrays; java.util.List<String> nameList = Arrays.asList("ken", "<unknown>", "taro", "ayumi", "hanako", "jin", "makoto", "<unknown>"); // Stream#parallelStreamを使う場合 nameList.parallelStream() .filter(s -> !s.equals("<unknown>")) .map(s -> s.toUpperCase()) .forEach(System.out::println); System.out.println("----"); // Stream#parallelを使う場合 nameList.stream() .parallel() .filter(s -> !s.equals("<unknown>")) .map(s -> s.toLowerCase()) .forEach(System.out::println);
streamsupportの場合
import java.util.Arrays; import java8.util.stream.StreamSupport; java.util.List<String> nameList = Arrays.asList("ken", "<unknown>", "taro", "ayumi", "hanako", "jin", "makoto", "<unknown>"); // StreamSupport#parallelStreamを使う場合 StreamSupport.parallelStream(nameList) .filter(s -> !s.equals("<unknown>")) .map(s -> s.toUpperCase()) .forEach(System.out::println); System.out.println("----"); // Stream#parallelを使う場合 StreamSupport.stream(nameList) .parallel() .filter(s -> !s.equals("<unknown>")) .map(s -> s.toLowerCase()) .forEach(System.out::println);
実行結果(並列処理しているので実行のたびに結果が変わります)
JIN HANAKO MAKOTO KEN AYUMI TARO ---- taro ken jin hanako makoto ayumi
Stream APIに存在しないStream操作
streamsupport
は、Java8標準のStream APIには存在しないStream操作も提供します
Stream#takeWhile - 条件に一致している間は取得する
Stream#takeWhile
は、Stream内の先頭要素から順に対象の条件に一致している間は値を取得しますが、
条件に一致しない要素が現れたタイミングで取得をしなくなります。
以下サンプルは、lengthが2以下の文字列が表れている間は取得し、それより大きい値が現れた瞬間に取得をしなくなる例です
サンプルコード
import java8.util.stream.Collectors; import java8.util.stream.RefStreams; List<String> destList1 = RefStreams.of("a", "b", "c", "de", "fgh", "ijkl", "mno", "pq", "r", "s") .takeWhile(s -> s.length() <= 2) .collect(Collectors.toList()); System.out.println(destList1);
実行結果
[a, b, c, de]
5番目の要素が条件に一致しなかったのでそれ以降の値は全て無視した状態になっています
Stream#dropWhile - 条件に一致している間は除外する
Stream#dropWhile
は、Stream内の先頭要素から順に対象の条件に一致している間は値を除外し、
条件に一致しない要素が現れたタイミングから取得を開始します。
(Stream#takeWhile
のが逆のような操作になります。)
以下サンプルは、lengthが2以下の文字列が表れている間は除外して、それより大きい値が現れた瞬間に取得を開始する例です
サンプルコード
import java8.util.stream.Collectors; import java8.util.stream.RefStreams; List<String> destList2 = RefStreams.of("a", "b", "c", "de", "fgh", "ijkl", "mno", "pq", "r", "s") .dropWhile(s -> s.length() <= 2) .collect(Collectors.toList()); System.out.println(destList2);
実行結果
[fgh, ijkl, mno, pq, r, s]
4番目の要素が条件に一致しなかったのでそれ以降の値を取得した状態になっています
Stream#findFirst - Stream内の先頭要素を取得する
Stream#findFirst
を使うことでStream内の先頭要素を取得することができます。
findFirst
の結果は、Optional
(java8.util.Optional
)型で返されます(要素が存在しない場合もあるので)。
以下サンプルでは、文字列のStreamからlengthが一定以上のものにfilterしてその中からfindFirst
で先頭の値を取得しています。
要素が存在しない場合はOptional#orElseメソッドで、"##None##"という文字列を返すようにしています
サンプルコード
import java8.util.stream.RefStreams; // 先頭要素が見つかる場合 String name1 = RefStreams.of("jon", "ken", "yui", "Taro", "Jiro", "Makoto", "Daigoro") .filter(s -> s.length() > 3) .findFirst() .orElse("##None##"); System.out.println("filtered result1: " + name1); // 先頭要素が見つからない場合 String name2 = RefStreams.of("jon", "ken", "yui", "Taro", "Jiro", "Makoto", "Daigoro") .filter(s -> s.length() > 10) .findFirst() .orElse("##None##"); System.out.println("filtered result2: " + name2);
実行結果
filtered result1: Taro filtered result2: ##None##
Stream操作以外にも
streamsupport
はJava標準のStream APIそのものをバックポートしているだけではなく以下のようなAPI(クラス、メソッド)についてもバックポートしています。
Java標準 | streamsupport |
---|---|
java.util.Optional |
java8.util.Optional |
java.util.StringJoiner |
java8.util.StringJoiner |
java.util.Objects |
java8.util.Objects |
streamsupportが提供するさらなる機能
streamsupport
はさらに以下のような機能についてもバックポートしています。
streamsupport-cfuture
- CompletableFuture API
streamsupport-atomic
- java.util.concurrent.atomicパッケージ
streamsupport-flow
- JDK 9 Flow API
streamsupport-literal
- Immutableなコレクションを生成するためのファクトリメソッドを提供(JEP 269: Convenience Factory Methods for Collections)
これらについては以下エントリを参照ください。
まとめ
streamsupport
を利用することでJava6, Java7を実行環境とする場合でも、Java8で提供されるStream API等の機能をほぼ取り込んだ状態の開発ができることが分かりました。