Java8から提供されているStream APIをJava6やJava7でも利用可能にしてくれるライブラリとしてLightweight-Stream-API
が存在しています。
このライブラリを使うことでJava8と同等かそれ以上の操作を利用することが可能となります。
Lightweight-Stream-APIの利用準備
MavenプロジェクトでLightweight-Stream-API
を利用する場合、pom.xmlに以下を追記します。
<dependency> <groupId>com.annimon</groupId> <artifactId>stream</artifactId> <version>1.1.3</version> </dependency>
また、最終的に生成したモジュールを実行する環境がJava6やJava7である場合は、Retrolambda
の設定も合わせて実施してください。(これによりラムダ式等を組み合わせることができます)
以下の各種サンプルでは基本的にRetrolambda
を組み合わせることを前提にした(ラムダ式やメソッド参照を利用した)コードを記述しています
Java8のStream APIの代わりに使う場合の基本的な使用方法
Java8のStream APIで提供されている操作は、Lightweight-Stream-API
でもほとんど同名の操作として提供されています。
そのためJava8のStream APIでのStream操作は、Lightweight-Stream-API
で置き換えることができます。
例えばCollectionをもとにしたStreamの操作を行う場合のJava8標準とLightweight-Stream-API
の対応関係は以下表のようになります
やりたいこと | Java8標準 | Lightweight-Stream-API |
---|---|---|
CollectionからStreamへの変換 | java.util.Collection#stream |
com.annimon.stream.Stream#of |
Streamのfilter / map / reduce / sort操作 | java.util.stream.Stream#filter, map, reduce, sorted |
com.annimon.stream.Stream#filter, map, reduce, sorted |
StreamからListへの変換 | java.util.stream.Stream#collect |
com.annimon.stream.Stream#collect |
基本的に同名の操作で提供されると書きましたが、例えばCollectionからStreamへの変換操作等は、異なる名前の操作で提供されています
以下にJava8標準とLightweight-Stream-APIを用いた場合の具体的なサンプルコードを比較して載せています。
(基本的にcom.annimon.stream
パッケージを利用することで、Java標準のStream APIとほぼ同じコードで実行できることが分かります)
■ListからStreamを生成して操作する(1)
名前(文字列)のListに対して以下の操作をするサンプルです
- 特定の文字列(<unknown>)を除外 (filter)
- 文字列を大文字に変換 (map)
- 辞書順でソート (sorted)
- 各々の要素(文字列)を標準出力に出力する (forEach)
Java8標準の場合
import java.util.Arrays; import java.util.List; 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);
Lightweight-Stream-APIの場合
import java.util.Arrays; import java.util.List; import com.annimon.stream.Stream; List<String> nameList = Arrays.asList("ken", "<unknown>", "taro", "ayumi", "hanako", "jin", "makoto", "<unknown>"); Stream.of(nameList) .filter(s -> !s.equals("<unknown>")) .map(s -> s.toUpperCase()) .sorted() .forEach(System.out::println);
実行結果(Java8標準, Lightweight-Stream-API のいずれの場合も同じ)
AYUMI HANAKO JIN KEN MAKOTO TARO
■ListからStreamを生成して操作する(2)
名前(文字列)のListに対して以下の操作をするサンプルです
- 特定の文字列(<unknown>)を除外 (filter)
- 文字列の文字数に変換(取得) (map)
- 値の大きい順(文字数の多い順)にソート (sorted)
- 重複(文字数が同じ)要素を除外(distinct)
- 結果をListに格納 (collect)
Java8標準の場合
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; List<String> nameList = Arrays.asList("ken", "<unknown>", "taro", "ayumi", "hanako", "jin", "makoto", "<unknown>"); List<Integer> destList = nameList.stream() .filter(s -> !s.equals("<unknown>")) .map(s -> s.length()) .sorted((x, y) -> y - x) .distinct() .collect(Collectors.toList()); System.out.println("destList: " + destList);
Lightweight-Stream-APIの場合
import java.util.Arrays; import java.util.List; import com.annimon.stream.Collectors; import com.annimon.stream.Stream; List<String> nameList = Arrays.asList("ken", "<unknown>", "taro", "ayumi", "hanako", "jin", "makoto", "<unknown>"); List<Integer> destList = Stream.of(nameList) .filter(s -> !s.equals("<unknown>")) .map(s -> s.length()) .sorted((x, y) -> y - x) .distinct() .collect(Collectors.toList()); System.out.println("destList: " + destList);
実行結果(Java8標準, Lightweight-Stream-API のいずれの場合も同じ)
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);
Lightweight-Stream-APIの場合
import com.annimon.stream.IntStream; int sum = IntStream.rangeClosed(1, 10) .filter(i -> i % 2 == 0) .sum(); System.out.println("sum: " + sum);
実行結果(Java8標準, Lightweight-Stream-API のいずれの場合も同じ)
sum: 30
Streamの操作を明示的に簡潔に記述するためのメソッド群
Lightweight-Stream-API
は、Java標準のStream APIでは提供されていないStream操作を提供しています。
Lightweight-Stream-API
の操作を利用することでJava標準の場合よりも明示的に簡潔にコードを書くことが可能となります。
サンプル内にPerson
クラスが登場しますが、以下のクラスとして定義されているものとします(省略のためLombokを利用)
import lombok.Value; @Value public class Person { private int id; private String name; }
■Stream#filterNot - 否定形のfilter操作
Java標準のStream#filter
を用いて特定の値を除外する否定形の操作をする場合は、若干分かりにくくなります。またメソッド参照を単純には使用できません。
Lightweight-Stream-API
では、filterNot
という否定形用のfilter操作が提供されています。
以下は、名前(文字列)のListから空文字列の値を取り除くサンプルです
Java8標準の場合
import java.util.function.Predicate; import java.util.stream.Collectors; List<String> nameList = Arrays.asList("Ken", "", "Taro", "Ayumi", "Hanako", "Iin", "Makoto", ""); // ラムダ式を使う場合 List<String> destList = nameList.stream() .filter(s -> !s.isEmpty()) .collect(Collectors.toList()); // メソッド参照を無理やり使う場合 List<String> destList = nameList.stream() .filter(((Predicate<String>) String::isEmpty).negate()) .collect(Collectors.toList()); System.out.println(destList);
Lightweight-Stream-APIの場合
import com.annimon.stream.Collectors; import com.annimon.stream.Stream; List<String> nameList = Arrays.asList("Ken", "", "Taro", "Ayumi", "Hanako", "Iin", "Makoto", ""); List<String> destList = Stream.of(nameList) .filterNot(String::isEmpty) .collect(Collectors.toList()); System.out.println(destList);
実行結果(Java8標準, Lightweight-Stream-API のいずれの場合も同じ)
[Ken, Taro, Ayumi, Hanako, Iin, Makoto]
■Stream#select - 指定の型にマッチする値だけを抽出する
Lightweight-Stream-API
のselect
を使用すると複数のclassの値が混在するStreamから指定のクラスの要素だけを抽出することができます。
以下は、複数の型の値が格納されたListからInteger型の要素を抽出するサンプルです
Java8標準の場合
List<?> srcList = Arrays.asList("Str001", 123, new Date(), "Str002", 456, 789L); List<?> destList = // <- List<integer> の型は記述できない srcList.stream() .filter(Integer.class::isInstance) .collect(Collectors.toList()); System.out.println(destList);
Lightweight-Stream-APIの場合
List<?> srcList = Arrays.asList("Str001", 123, new Date(), "Str002", 456, 789L); List<Integer> destList = Stream.of(srcList) .select(Integer.class) .collect(Collectors.toList()); System.out.println(destList);
実行結果(Java8標準, Lightweight-Stream-API のいずれの場合も同じ)
[123, 456]
■Stream#sortBy - メソッドを指定してのソート
Lightweight-Stream-API
が提供するsortBy
を利用することで、特定のメソッドの結果でのソートを直観的に記述することが可能となります。
以下サンプルはPerson
クラスのname順(getName
の結果による)ソートを行う例です
Java8標準の場合
import java.util.Comparator; import java.util.stream.Collectors; Person p1 = new Person(1, "Taro"); Person p2 = new Person(2, "Jiro"); Person p3 = new Person(5, "Hanako"); Person p4 = new Person(100, "Ayako"); Person p5 = new Person(11, "Beck"); Person p6 = new Person(3, "Taro"); List<Person> srcList = Arrays.asList(p1, p2, p3, p4, p5, p6); List<Person> destList = srcList.stream() .sorted(Comparator.comparing(Person::getName)) .collect(Collectors.toList()); System.out.println(destList);
Lightweight-Stream-APIの場合
import com.annimon.stream.Collectors; import com.annimon.stream.Stream; Person p1 = new Person(1, "Taro"); Person p2 = new Person(2, "Jiro"); Person p3 = new Person(5, "Hanako"); Person p4 = new Person(100, "Ayako"); Person p5 = new Person(11, "Beck"); Person p6 = new Person(3, "Taro"); List<Person> srcList = Arrays.asList(p1, p2, p3, p4, p5, p6); List<Person> destList = Stream.of(srcList) .sortBy(Person::getName) .collect(Collectors.toList()); System.out.println(destList);
実行結果(Java8標準, Lightweight-Stream-API のいずれの場合も同じ)
name順にソートされていることが分かります
[Person(id=100, name=Ayako), Person(id=11, name=Beck), Person(id=5, name=Hanako), Person(id=2, name=Jiro), Person(id=1, name=Taro), Person(id=3, name=Taro)]
■Stream#groupBy - メソッドを指定してgroup by
Lightweight-Stream-API
が提供するgroup by
を利用することで、その名の通りにグループ化(group by)を直観的に実施することが可能となります。
Java標準のStream APIでは、中間操作としてのgroup byが無いため、終端操作のcollectでCollectors#groupingBy
を実施する必要があります。そのため、その後にさらにStream操作をしたい場合等はコードが単純ではなくなります。
これに対してLightweight-Stream-APIのgroup by
は中間操作として提供されているため、このような問題がクリアされています。
以下サンプルはPerson
クラスのname(getName
の結果による)でgroup byを実施する例です。
Java8標準の場合
import java.util.stream.Collectors; Person p1 = new Person(1, "Taro"); Person p2 = new Person(2, "Jiro"); Person p3 = new Person(5, "Hanako"); Person p4 = new Person(100, "Ayako"); Person p5 = new Person(11, "Beck"); Person p6 = new Person(3, "Taro"); Person p7 = new Person(101, "Ayako"); List<Person> srcList = Arrays.asList(p1, p2, p3, p4, p5, p6, p7); srcList.stream() .collect(Collectors.groupingBy(Person::getName)) .entrySet() .stream() .forEach(System.out::println);
Lightweight-Stream-APIの場合
import com.annimon.stream.Stream; Person p1 = new Person(1, "Taro"); Person p2 = new Person(2, "Jiro"); Person p3 = new Person(5, "Hanako"); Person p4 = new Person(100, "Ayako"); Person p5 = new Person(11, "Beck"); Person p6 = new Person(3, "Taro"); Person p7 = new Person(101, "Ayako"); List<Person> srcList = Arrays.asList(p1, p2, p3, p4, p5, p6, p7); Stream.of(srcList) .groupBy(Person::getName) .forEach(System.out::println);
実行結果(Java8標準, Lightweight-Stream-API のいずれの場合も同じ)
Ayako=[Person(id=100, name=Ayako), Person(id=101, name=Ayako)] Beck=[Person(id=11, name=Beck)] Hanako=[Person(id=5, name=Hanako)] Taro=[Person(id=1, name=Taro), Person(id=3, name=Taro)] Jiro=[Person(id=2, name=Jiro)]
Lightweight-Stream-APIでは提供されていないものもある
Java8標準のStream APIでは提供されているが、Lightweight-Stream-API
では提供されない操作も存在しています。
例えば、parallel
などは、その代表格と言えるかもしれません
さらなるStream操作
Lightweight-Stream-API
では既に紹介したStream操作以外にもJava8標準では対応する操作が無いようなものもAPIとして提供されています。
それらについては以下エントリを参照ください。