2
5

More than 1 year has passed since last update.

【Java】Stream API 1000本ノック

Posted at

環境

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も並列になる。

2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5