環境
java 17 (amazon corretto)
filter
リストの中から任意の条件に合致する要素だけをフィルタリングする。
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.filter(e -> e % 2 == 0)
.forEach(System.out::println);
2
4
6
8
10
map
リストの中身を加工して返す。
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.map(e -> e * 100)
.forEach(System.out::println);
100
200
300
400
500
600
700
800
900
1000
mapToInt
Stringのリストをintに変換して返す。
int[] mappedToInt = Stream.of("1", "2", "3", "4", "5", "6", "7", "8", "9", "10")
.mapToInt(Integer::parseInt)
.toArray();
Arrays.stream(mappedToInt).forEach(System.out::println);
1
2
3
4
5
6
7
8
9
10
mapToIntの戻り値はIntStreamであり、mapの戻り値のStreamとは異なるため、toList()でのリスト変換ができない。
代わりに、
<R> R collect(Supplier<R> supplier,
ObjIntConsumer<R> accumulator,
BiConsumer<R, R> combiner);
が用意されているので、
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll)
とすればリスト変換も可能。
ちょっとめんどくさい。せっかく最近のJavaアプデでcollect(Collectors.toList())
-> toList()
へと簡略化されたので、IntStreamも対応してほしいところ。
mapToIntの親戚にmapToLong, mapToDoubleもいる。(割愛)
flatMap
ネストしたリストをフラット(ネストのない)なリストに変換する。
var devices = List.of(
List.of("iPhone13, iPhone13 Pro", "iPhone 13 mini"),
List.of("Galaxy", "Arrows", "AQUOS"));
var flatMapped = devices.stream().flatMap(Collection::stream).toList();
System.out.println(flatMapped.toString()); // [iPhone13, iPhone13 Pro, iPhone 13 mini, Galaxy, Arrows, AQUOS]
var flatMapped = devices.stream().flatMap(Collection::stream).toList();
は、メソッド参照せずに書くと以下。
var flatMapped = devices.stream().flatMap(device -> device.stream()).toList();
他にも↓のような使い方もある。
flatMapToInt
TBD
distinct
sorted
peek
var list = Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.toList();
System.out.println(list);
実行結果↓
> Task :Main.main()
Filtered value: three
Mapped value: THREE
Filtered value: four
Mapped value: FOUR
[THREE, FOUR]
Stream処理のデバッグ用に用意されているっぽい。
実装中のデバッグとして使うのはありだが、プロダクトコードにpeek自体が記載されることはないような気がする。
↑のコードで少し勉強になったのはStreamの処理の順序。
↓のように
Filtered value: three
Filtered value: four
Mapped value: THREE
Mapped value: FOUR
filter -> mapの順で処理されてると思いこんでいたが、実際は違うのね。
limit
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).limit(5).forEach(System.out::println);
1
2
3
4
5
リストを任意のサイズに絞ることで、「n番目までの要素」を別リストとして切り出せる。
後述のskipと相性がいい。
skip
var list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
var skipped = list.stream().skip(3).toList();
System.out.println(skipped);
最初のn個をスキップしたリストを返す。
limitと組み合わせることで、柔軟にリストを取り出すことができる。
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).skip(3).limit(2).forEach(System.out::println);
4
5
takeWhile
Stream.of(1, 2, 3, 1, 2, 3).takeWhile(e -> e < 3).forEach(System.out::println);
1
2
条件に一致する要素だけを取り出す。
collentionの1番目の要素が条件に一致しない場合は、何も取り出されない。
Stream.of(1, 2, 3, 1, 2, 3).takeWhile(e -> e%2 == 0).forEach(System.out::println);
// ↑何も出力されない
なお、filterと違って、条件に一致しない要素が見つかった時点で走査をやめる。
var takeWhile = Stream.of(1, 2, 3, 1, 2, 3).takeWhile(e -> e < 3).toList();
System.out.println(takeWhile); //[1, 2]
var filtered = Stream.of(1, 2, 3, 1, 2, 3).filter(e -> e < 3).toList();
System.out.println(filtered); //[1, 2, 1, 2]
dropWhile
条件に一致する要素を除外する。takeWhileの逆版。
Stream.of(1, 2, 3, 1, 2, 3).dropWhile(e -> e < 3).forEach(System.out::println);
3
1
2
3
forEach
Streamを習ったときに恐らく一番最初に知るであろうもの。
この記事でもすでに散々使っている。
Stream.of("yamada", "suzuki", "watanabe")
.forEach(e -> {
System.out.println("My name is %s".formatted(e));
});
My name is yamada
My name is suzuki
My name is watanabe
forEachOrdered
並列ストリームを用いる際に、streamの順番通りに処理したいときに使う。
IntStream.range(0, 10).forEach(System.out::print);//順次ストリーム
System.out.println();
IntStream.range(0, 10).parallel().forEach(System.out::print);//並列ストリーム
System.out.println();
IntStream.range(0, 10).parallel().forEachOrdered(System.out::print);//並列ストリーム
0123456789
6578914302
0123456789
<参考>
toArray
StreamからArrayに変換する。引数を省略するとObject[]が返る。
String[] array = Stream.of("a", "b", "c").toArray(String[]::new);
reduce
この記事がわかりやすかった。
記事にもあるが、
reduceはStream APIの中でも最も難解な関数のひとつだと思います。mapやfilterは使っているうちに慣れたという人でも、reduceをバリバリ使う人はあまりいないのではないでしょうか。
reduceは非常に難解。
reduceには引数違いの3種類の兄弟がいる。
Optional<T> reduce(BinaryOperator<T> accumulator)
javadocに以下の記載がある。
https://docs.oracle.com/javase/jp/17/docs/api/java.base/java/util/stream/Stream.html#reduce(java.util.function.BinaryOperator)
boolean foundAny = false;
T result = null;
for (T element : this stream) {
if (!foundAny) {
foundAny = true;
result = element;
}
else
result = accumulator.apply(result, element);
}
return foundAny ? Optional.of(result) : Optional.empty();
※BinaryOperator<T>
はざっくりいうとR apply(T, T)
という感じ。
streamに対して引数accumulator
で指定した畳み込み処理を実施し、結果があればそのOptionalを返す(なければOptional.empty)。
var sum1 = Stream.of(1, 2, 3, 4, 5).reduce((a, b) -> a + b);
// Optional[15]
var sum2 = Stream.of(1, 2, 3, 4, 5).filter(e -> e > 10).reduce((a, b) -> a + b);
// Optional.empty
T reduce(T identity, BinaryOperator<T> accumulator)
javadocによると、以下と同義。
https://docs.oracle.com/javase/jp/17/docs/api/java.base/java/util/stream/Stream.html#reduce(T,java.util.function.BinaryOperator)
T result = identity;
for (T element : this stream)
result = accumulator.apply(result, element)
return result;
第一引数のidentity
は単位元。
var sum3 = Stream.of(1, 2, 3, 4, 5).reduce(10, (a, b) -> a + b);
// 25
前項のOptional<T> reduce(BinaryOperator<T> accumulator)
した結果が空の場合はOptional.emptyを返していたが、このreduceはからの場合はidentity
を返す。
var sum4 = Stream.of(1, 2, 3, 4, 5).filter(e -> e > 10).reduce(10, (a, b) -> a + b);
// 10
<U> U reduce(U var1, BiFunction<U, ? super T, U> var2, BinaryOperator<U> var3);
javadocによると以下と同義。
https://docs.oracle.com/javase/jp/17/docs/api/java.base/java/util/stream/Stream.html#reduce(U,java.util.function.BiFunction,java.util.function.BinaryOperator)
U result = identity;
for (T element : this stream)
result = accumulator.apply(result, element)
return result;
ただし、順次実行の制約が課されるわけではありません。
identity値はコンバイナ関数の単位元でなければいけません。 つまり、すべてのuについて、combiner(identity, u)がuに等しくなります。 さらに、combiner関数はaccumulator関数と互換性がある必要があります。すべてのuとtについて、次が成り立つ必要があります。
combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)
collect
R collect(Collector super T, A, R> var1);
List<String> list = Stream.of("a", "b", "c").collect(Collectors.toList());
Map<String, String> map = Stream.of("a", "b", "c")
.collect(Collectors.toMap(String::toUpperCase, e -> e));
var1
にCollectorsのメソッドを渡して、ListやMapを生成できる。
だが、1つ目のListは、Java17より
List<String> list = Stream.of("a", "b", "c").toList();
とできるので、こっちをつかったほうがいい。
R collect(Supplier var1, BiConsumer var2, BiConsumer var3);
TBD
min
var minNum = Stream.of(1, 2, 3, 4, 5).min(Comparator.naturalOrder());
var minStr = Stream.of("a", "b", "c").min(Comparator.naturalOrder());
System.out.println(minNum);
System.out.println(minStr);
Optional[1]
Optional[A]
最小値を取得する。
Stringの場合は辞書順で最も若いものを取得する。
max
var maxNum = Stream.of(1, 2, 3, 4, 5).max(Comparator.naturalOrder());
var maxStr = Stream.of("a", "b", "c").max(Comparator.naturalOrder());
System.out.println(maxNum);
System.out.println(maxStr);
Optional[5]
Optional[c]
最大値を取得する。
Stringの場合は辞書順で最も後のものを取得する。
count
var count = Stream.of(1, 2, 3, 4, 5).count();
System.out.println(count);
5
streamの要素の個数を取得する。ただそれだけ。
anyMatch
boolean result1 = Stream.of("apple", "baggage", "cake").anyMatch(e -> e.startsWith("a"));
// true
boolean result2 = Stream.of("apple", "baggage", "cake").anyMatch(e -> e.startsWith("z"));
// false
条件に合致する要素が1個以上あればtrueを返す。
allMatch
boolean result = Stream.of("apple", "baggage", "cake").allMatch(e -> e.endsWith("e"));
// true
boolean result2 = Stream.of("apple", "baggage", "cake").allMatch(e -> e.startsWith("a"));
// false
全ての要素が条件に合致する場合のみtrueを返す。
noneMatch
boolean result = Stream.of("apple", "baggage", "cake").noneMatch(e -> e.startsWith("z"));
// true
boolean result2 = Stream.of("apple", "baggage", "cake").noneMatch(e -> e.endsWith("e"));
// false
全ての要素が条件に合致しない場合にtrueを返す。
findFirst
var e = Stream.of("a", "b", "c").findFirst();
System.out.println(e);
Optional[a]
streamの中から1番目の要素をOptional型で取り出す。
var result = Stream.of("a", "b", "c").filter(e -> e.length() > 1).findFirst();
System.out.println(result);
Optional.empty
結果がなければempty
System.out.println("findFirst(直列)");
for (int i = 0; i < 20; i++) {
System.out.println(Stream.of("a", "b", "c", "d", "e", "f")
.filter(e -> e.equals("c"))
.findFirst());
}
System.out.println("findFirst(並列)");
for (int i = 0; i < 20; i++) {
System.out.println(Stream.of("a", "b", "c", "d", "e", "f")
.parallel()
.filter(e -> e.equals("c"))
.findFirst());
}
findFirst(直列)
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
findFirst(並列)
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
Optional[c]
findFirst
は直列・並列Streamどちらの場合も、1番目の要素を返す。
findAny
var result = Stream.of("a", "b", "c", "d", "e", "f").findAny();
System.out.println(result);
Optional[a]
streamのなかから任意の要素をOptional型で返す。
System.out.println("findAny(直列)");
for (int i = 0; i < 20; i++) {
System.out.println(Stream.of("a", "b", "c", "d", "e", "f").findAny());
}
System.out.println("findAny(並列)");
for (int i = 0; i < 20; i++) {
System.out.println(Stream.of("a", "b", "c", "d", "e", "f").parallel().findAny());
}
findAny(直列)
Optional[a]
Optional[a]
Optional[a]
Optional[a]
Optional[a]
Optional[a]
Optional[a]
Optional[a]
Optional[a]
Optional[a]
Optional[a]
Optional[a]
Optional[a]
Optional[a]
Optional[a]
Optional[a]
Optional[a]
Optional[a]
Optional[a]
Optional[a]
findAny(並列)
Optional[d]
Optional[d]
Optional[d]
Optional[d]
Optional[d]
Optional[d]
Optional[d]
Optional[d]
Optional[d]
Optional[d]
Optional[d]
Optional[d]
Optional[d]
Optional[d]
Optional[b]
Optional[d]
Optional[d]
Optional[d]
Optional[d]
Optional[d]
findAnyはfindFirstより高速だが、並列ストリームで実行した場合に返却される要素がランダムになる。
builder
Stream.builder().add(Arrays.asList("a", "b", "c")).build().forEach(System.out::println);
[a, b, c]
Streamを生成するbuilder。
Stream.of
があるので、使う場面少なそう。
empty
var empty = Stream.empty();
空のStream<Object>
を返す。
of
Stream.of("a", "b", "c").forEach(System.out::println);
a
b
c
任意の直列streamを返す。
Stream.of(1, null).forEach(System.out::println);
1
null
nullが混じっていても大丈夫。
Stream.of(null).forEach(System.out::println);
Exception in thread "main" java.lang.NullPointerException: Cannot read the array length because "array" is null
at java.base/java.util.Arrays.stream(Arrays.java:5428)
at java.base/java.util.stream.Stream.of(Stream.java:1426)
at Main.main(Main.java:10)
nullだけだとぬるぽで落ちる。
ofNullable
nullセーフなof。
iterate
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
seed
は初期値、f
は次の要素を生成する関数。
※UnaryOperatorはざっくり言うと R apply(T t)
Stream.iterate(0, n -> n + 1).limit(10).forEach(System.out::println);
0
1
2
3
4
5
6
7
8
9
順序づけされたstreamを生成して返す。
iterateは無限順次ストリームを生成するため、.limit(10)
などをつけておかないと無限にprintlnで出力されてしまう。
static <T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
Stream.iterate(0, n -> n < 10, n -> n + 1).forEach(System.out::println);
0
1
2
3
4
5
6
7
8
9
hasNext
にiterateを継続するかどうかの述語を指定することで、stream生成を途中で終了させることができる。
generate
static <T> Stream<T> generate(Supplier<? extends T> s)
Stream.generate(Math::random).limit(10).forEach(System.out::println);
0.6367534370722193
0.4060680438123063
0.7259277359859951
0.3210005395437554
0.11408977146578747
0.13146103212325666
0.645913985897788
0.31120448118461697
0.19032175343686475
0.8638467167816094
iterateと似ているが、generateは順序づけのない無限順次ストリームを生成して返す。
concat
Stream.concat(Stream.of(1, 2, 3), Stream.of(4, 5, 6)).forEach(System.out::println);
1
2
3
4
5
6
2つのstreamを連結する。
Stream.concat(Stream.of(1, 2, 3).parallel(), Stream.of(4, 5, 6)).forEach(System.out::println);
4
1
3
2
5
6
連結対象の少なくとも一方が並列streamの場合、連結されたstreamも並列になる。