Java Stream API とは
Java SE8 から追加されたイテレーションの拡張 API です。
基本的な操作の流れは
- stream を生成
- 「中間操作」を任意の回数実施してコレクションの中身を操作
- 「終端操作」を実施してコレクションに操作を適用したり、中間操作で変更されたコレクションを取得
例) 整数のリストから偶数のみを大きい順に取り出す
List<Integer> list = Arrays.asList(5, 2, 8, 9, 3, 4);
List<Integer> evenList = list.stream()
.filter(e -> (e % 2) == 0) // (中間操作) 偶数だけの stream を取得
.sorted(reverseOrder()) // (中間操作) 降順にソート
.collect(Collectors.toList()); // (終端操作) リストとして取得
stream を使い回すことは出来ないので注意。
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> stream = list.stream().filter(e -> (e % 2) == 0); // 偶数だけの stream を取得
int max = stream.max(naturalOrder()).orElse(0); // list 内の偶数の最大値
int min = stream.min(naturalOrder()).orElse(0); // stream の再利用は禁止されており IllegalStateException 発生
// 下記のような実装は可能
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenList = list.stream()
.filter(e -> (e % 2) == 0) // 偶数だけの stream を取得
.collect(Collectors.toList()); // いったんリストを生成
int max = evenList.stream().max(naturalOrder()).orElse(0);
int min = evenList.stream().min(naturalOrder()).orElse(0);
Stream の各種操作
ストリームの生成
コレクションをもとに stream を生成
Stream<E> stream()
※ Collection のメソッド
stream を利用する際はこれが一番利用されていると思います。
冒頭の例でも使用している通り、list.stream()
のように使用します。
stream を繋げる
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
2つの stream を繋げる。
3つ以上繋げたい場合は of().flatMap() を利用する。
Stream s = Stream.concat(s1, s2);
Stream s = Stream.of(s1, s2, s3).flatMap(e -> e);
builder から生成
Stream s = Stream.builder().add(1).add(2).add(3).build();
空の stream
static <T> Stream<T> empty()
Stream s = Stream.empty();
指定要素の順序付けされた stream
static <T> Stream<T> of(T t)
@SafeVarargs static <T> Stream<T> of(T... values)
Stream s1 = Stream.of("a");
Stream s2 = Stream.of("a", "b", "c");
単一要素の stream (要素が null ならば空の stream) (Java 9 以降)
static <T> Stream<T> ofNullable(T t)
Stream emptyStream = Stream.ofNullable(null);
Stream oneEleStream = Stream.ofNullable(1);
順序付けの無い無限順次ストリームを生成
static <T> Stream<T> generate(Supplier<? extends T> s)
指定された Supplier によって生成される要素を含む、順序付けされていない無限順次ストリームを生成する。
Stream s = Stream.generate(random::nextInt); // 乱数の無限順次ストリーム
初期要素に繰り返し関数を適用することで生成される順序付け無限順次ストリームを生成 (Java 9 以降)
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
一つ目の要素は seed、二つ目以降は関数 f が繰り返し適用された無限順次ストリームを生成する。
要素は seed, f(seed), f(f(seed)), ... といった形になる。
// "a", "aa", "aaa", ... といった無限順次ストリーム
Stream s = Stream.iterate("a", e -> e + "a");
終了条件のある iterate (Java 9 以降)
static <T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
基本は iterate(T seed, UnaryOperator<T> f)
と同じ。
hasNext が false を返すと要素の生成を停止する (hasNext が false となる要素は stream に含まれない)。
Stream s = Stream.iterate("a", e -> e.length() < 5, e -> e + "a");
中間操作
フィルタ系
重複を削除
Stream<T> distinct()
list = list.stream().distinct().toList(); // list から重複を除く
先頭から n 個の要素を削除
Stream<T> skip(long n)
Stream s = list.stream().skip(3); // 先頭から 3 つの要素を除く
条件を指定してフィルタリング
Stream<T> filter(Predicate<? super T> predicate)
条件に一致する要素のみを返す。
list = list.stream().filter(e -> e < 10).toList(); // 10 未満の要素のみにする
条件に一致する間要素を取得 (Java 9 以降)
default Stream<T> takeWhile(Predicate<? super T> predicate)
先頭要素から見て行き、要素が predicate を満たす間その要素を取得する。
predicate が false になる要素が見つかった時点でその要素以降は削除される。
HashSet など順不同の場合は動作が非決定的になる。
list = list.stream().takeWhile(e -> e < 10).toList();
// list の元の中身が [1, 9, 10] -> [1, 9]
// [9, 10, 1] -> [9]
// [10, 9, 1] -> []
条件に一致しなくなるまで要素を削除 (Java 9 以降)
Stream<T> dropWhile(Predicate<? super T> predicate)
先頭要素から見て行き、要素が predicate を満たす間その要素を削除する。
predicate が false になる要素が見つかった時点で削除を止め、残った要素を返す。
HashSet など順不同の場合は動作が非決定的になる。
list = list.stream().dropWhile(e -> e.startsWith("f")).toList();
// list の元の中身が ["foo", "fuga", "hoge"] -> ["hoge"]
// ["foo", "hoge", "fuga"] -> ["hoge", "fuga"]
// ["hoge", "foo", "fuga"] -> ["hoge", "foo", "fuga"]
指定の要素数に切り詰める
Stream<T> limit(long maxSize)
先頭から maxSize 以内の長さになるように要素を切り詰める。
Stream s = Stream.generate(() -> "a").limit(3);
ソート系
自然順序に従ってソート
Stream<T> sorted()
var list = Stream.of(5, 3, 1, 2, 4)
.sorted() // [1, 2, 3, 4, 5]
.toList();
指定の順序でソート
Stream<T> sorted(Comparator<? super T> comparator)
指定された comparator に従ってソート。
itemList = itemList.stream()
.sorted(Comparator.comparing(Item::id).reversed()) // ID の降順
.toList();
Stream の置き換え系
指定した関数を適用した結果から構成される stream に置き換える
<R> Stream<R> map(Function<? super T,? extends R> mapper)
各要素に mapper を適用して得た stream を返す。
public record Person(String name, List<String> hobby){}
List<Person> list = Arrays.asList(
new Person("山田", Arrays.asList("けん玉")),
new Person("鈴木", Arrays.asList("散歩", "映画")));
Stream s = list.stream().map(Person::hobby); // [ ["けん玉"], ["散歩", "映画"] ]
map の int 版
IntStream mapToInt(ToIntFunction<? super T> mapper)
int sum = list.stream()
.mapToInt(Integer::parseInt)
.sum(); // 6
map の long 版
LongStream mapToLong(ToLongFunction<? super T> mapper)
List<String> list = Arrays.asList("1000000000", "2000000000", "3000000000");
long sum = list.stream()
.mapToLong(Long::parseLong)
.sum(); // 6000000000
map の double 版
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)
List<String> list = Arrays.asList("0.1", "2.3", "4");
Double sum = list.stream()
.mapToDouble(Double::parseDouble)
.sum(); // 6.4
指定したマッピング関数で得られる stream に置き換える
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
①各要素に mapper を適用して stream を得て、②それらをひとまとめにした stream を返す。
map(Function<? super T,? extends R> mapper)
との違いはフラットな stream として返すかどうか。
→ ①の時点で結果を返すのが map、①の結果をフラットな状態にして返すのが flatMap
イメージ:
[Aさんのペットリスト, Bさんのペットリスト]
→ [ [猫A, 猫B], [猫C, 猫D] ] ① 各ペットリストから猫リストの stream を得る
→ [猫A, 猫B, 猫C, 猫D] ② 得た結果をフラットな stream として返す
public record Pet(List<Dog> dogList, List<Cat> catList){}
public record Dog(String name){}
public record Cat(String name){}
List<Cat> catList = petList.stream()
.flatMap(pet -> pet.catList().stream()) // Stream<Cat>
// .map(pet -> pet.catList().stream()) の場合は Stream<Stream<Cat>> が返る
.toList();
flatMap の int 版 (IntStream への置き換え)
IntStream flatMapToInt(Function<? super T,? extends IntStream> mapper)
int[][] intArray = {{1, 2}, {3, 4, 5}};
int sum = Arrays.stream(intArray)
.flatMapToInt(Arrays::stream)
.sum(); // 15
flatMap の Long 版 (LongStream への置き換え)
LongStream flatMapToLong(Function<? super T,? extends LongStream> mapper)
long[][] intArray = {{1L, 2L}, {3L, 4L, 5L}};
long sum = Arrays.stream(intArray)
.flatMapToLong(Arrays::stream)
.sum(); // 15L
flatMap の Double 版 (DoubleStream への置き換え)
DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper)
double[][] intArray = {{0.1, 0.2}, {1.3, 2.1, 5}};
double sum = Arrays.stream(intArray)
.flatMapToDouble(Arrays::stream)
.sum(); // 8.7
各要素を複数の要素で置き換えた結果で構成されるストリームへ置き換え (Java 16 以降)
default <R> Stream<R> mapMulti(BiConsumer<? super T,? super Consumer<R>> mapper)
置換要素を受け入れる consumer とともに、指定されたマッピング関数を各要素に適用する。
private record Person(String name, List<Hobby> hobbies){}
private record Hobby(List<String> outdoors, List<String> indoors){}
List<Person> list = List.of(
new Person("山田太郎", List.of(new Hobby(List.of("キャンプ", "釣り"), List.of("マンガ")))),
new Person("鈴木一郎", List.of(new Hobby(List.of("釣り"), List.of("読書", "映画鑑賞"))))
);
// 全ての趣味を表示する (重複は削除)
list.stream()
.flatMap(person -> person.hobbies().stream())
.mapMulti((hobby, consumer) -> {
hobby.outdoors().forEach(consumer);
hobby.indoors().forEach(consumer);
})
.distinct()
.forEach(e -> System.out.print(e + " ")); // キャンプ 釣り マンガ 読書 映画鑑賞
mapMulti の IntConsumer 版 (Java 16 以降)
default IntStream mapMultiToInt(BiConsumer<? super T,? super IntConsumer> mapper)
IntStream is = list.stream()
.mapMultiToInt((e, consumer) -> consumer.accept(e));
mapMulti の LongConsumer 版 (Java 16 以降)
default LongStream mapMultiToLong(BiConsumer<? super T,? super LongConsumer> mapper)
LongStream ls = list.stream()
.mapMultiToLong((e, consumer) -> consumer.accept(e));
mapMulti の DoubleConsumer 版 (Java 16 以降)
default DoubleStream mapMultiToDouble(BiConsumer<? super T,? super DoubleConsumer> mapper)
DoubleStream ls = list.stream()
.mapMultiToDouble((e, consumer) -> consumer.accept(e));
stream をそのまま返し、消費される各要素に指定されたアクションを実行
Stream<T> peek(Consumer<? super T> action)
stream の要素からなる stream を返す。
元 stream から消費される各要素に対して指定された非干渉アクションを実行する。
要素がパイプライン内の特定ポイントを通過する際に、その内容を確認するようなデバッグのサポートが目的。
list = list.stream()
.filter(e -> e instanceof Integer)
// int だけになっていることをプリントデバッグで確認
.peek(e -> System.out.println("Filtered value: " + e))
.collect(Collectors.toList());
終端操作
評価系
全ての要素が条件を満たすか?
boolean allMatch(Predicate<? super T> predicate)
ストリームの全要素が predicate に一致するなら true, 一致しない要素があるならば false を返す。
ストリームが空の場合は true を返す。
// 全て偶数: true, 奇数が含まれる: false
list.stream().allMatch(e -> e % 2 == 0);
全ての要素が条件を満たさないか?
boolean noneMatch(Predicate<? super T> predicate)
predicate に一致する要素が存在しなければ true, ひとつでも一致する要素があるならば false を返す。
ストリームが空の場合は true を返す。
// 偶数を含まない: true, 偶数が含まれる: false
list.stream().noneMatch(e -> e % 2 == 0);
条件を満たす要素がひとつでも存在するか?
boolean anyMatch(Predicate<? super T> predicate)
ストリームのいずれかの要素が predicate に一致するなら true, 一致しないなら false を返す。
ストリームが空の場合は false を返す。
// 文字列 "err" という要素が存在する: true, 存在しない: false
list.stream().anyMatch(Predicate.isEqual("err"));
要素の取得系
配列にする
Object[] toArray()
<A> A[] toArray(IntFunction<A[]> generator)
generator を指定しなければ Object の配列になります。
Object[] oArray = Stream.of(1, 2, 3).toArray();
Integer[] iArray = Stream.of(1, 2, 3).toArray(Integer[]::new);
List にする (Java 16 以降)
default List<T> toList()
stream が順序付けされている場合はその順序になる。
List<Integer> list = Stream.of(1, 2, 3).toList();
指定のコレクションに変換する
<R, A> R collect(Collector<? super T, A, R> collector)
steam の要素に対する 可変リダクション操作 を行う。
各種リダクション操作はこちらを参照。
主観で一番利用頻度の高いものは Collectors.toList()
でしたが、Java 16 から Stream#toList()
が使用できるので利用頻度は落ちそう。
Collectors.groupingBy()
で Map にするために利用することがメインになるかも?
// 中間操作を適用した後、リストに詰め直す
list = list.stream()./* 中間操作 */.collect(Collectors.toList());
// city を key にして Map に分類
Map<String, List<Person>> peopleByCity = personList.stream()
.collect(Collectors.groupingBy(Person::city));
いずれか一つの要素を取得
Optional<T> findAny()
要素のうちいずれか一つを Optional で返す。
stream が空の場合は空の Optional を返す。
非決定的であり、同じソースに対して呼び出すたびに異なる結果が返る可能性がある。
Optional o = list.stream().findAny();
先頭の要素を取得
Optional<T> findFirst()
最初の要素を Optional で返す。
stream が空の場合は空の Optional を返す。
Optional o = list.stream().findFirst();
最大値を取得
Optional<T> max(Comparator<? super T> comparator)
指定された Comparator に従い、最大値を Optional で返す。
Optional<Integer> max = list.stream().max(Comparator.naturalOrder());
最小値を取得
Optional<T> min(Comparator<? super T> comparator)
指定された Comparator に従い、最小値を Optional で返す。
Optional<Integer> min = list.stream().min(Comparator.naturalOrder());
その他処理の適用系
要素数を数える
long count()
long cnt = list.stream().count(); // list の要素数
処理を適用する
void forEach(Consumer<? super T> action)
stream の各要素に対して action を実行する。
検出順序は保証されない。
list.stream().forEach(System.out::println);
処理をストリームの検出順に適用する
void forEachOrdered(Consumer<? super T> action)
stream の検出順序が定義されている場合はその順序で action が適用される forEach()。
list.stream().forEachOrdered(System.out::println);
結合的累積関数を使用してリダクションされた Optional を返す
Optional<T> reduce(BinaryOperator<T> accumulator)
accumulator を適用した結果を Optional で返す。
// 文字列結合
String s = Stream.of("a", "b", "c")
.reduce((result, e) -> result + e)
.orElse("");
// Set の和集合を求める
List<Set<Integer>> list = Arrays.asList(set1, set2, ...);
Set<Integer> unionSet = list.stream()
.reduce((set, value) -> {
Set<Integer> tmp = new HashSet<>(set);
tmp.addAll(value);
return tmp;
})
.orElse(new HashSet<>());
reduce の初期値あり版
T reduce(T identity, BinaryOperator<T> accumulator)
String s = Stream.of("a", "b", "c")
.reduce("String: ", (acc, e) -> acc + e); // "String: abc"
reduce で変換機能を兼ねた累積関数を利用する
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
初期値 identity として累積関数 accumulator を適用する。
accumulator では T
から U
への変換機能を有する。
並列 stream の場合は accumulator の適用結果が複数生まれるので、combiner を用いて結合する (list.stream()
の場合は combiner は無視される)。
List<String> animals = Arrays.asList("dog", "cat", ...);
long dogCount = animals.parallelStream()
.reduce(0L, (count, animal) -> "dog".equals(animal) ? count + 1 : count, Long::sum);
上記コードの補足説明
- identity: 初期値は 0 とする
- accumulator: 要素が "dog" であればその数を数える (String から long への変換機能 & 累積機能)
-
combiner: accumulator で得たカウント数を合計する
- 並列 stream の場合は accumulator の結果が複数生まれるため
参考資料