LoginSignup
18
10

More than 5 years have passed since last update.

絵で理解するリスト処理 - java8 stream / javaslang -

Last updated at Posted at 2018-12-10

「java8 で stream やってみたけど慣れねー」とか「ちょっと難しいことしようとするとできねー」ってのを良く聞くのでまとめてみるよ。

型さえ把握すれば8割方わかった様なもんなので、それを絵にしてみるよ。

ちなみに、記事は長いけど最初の[本編 -って見出しの箇所まで読めば十分だ!

やっぱ冷静に考えると長すぎるので、分割した。

[本編 - java8 stream]: filter, map, reduce

基本の3手を押さえよう。

reduceには馴染みがないかもしれないけど、Google が提唱したMapReduceモデルってのでmapとは一緒に語られるよ。-> wiki

以下の例題を使って見て行くよ。

/*
 * 以下の様なログがある
 * ラベル もしくは ラベル:millisec の形式で1行ずつ出力される
 *
 * millisec の総和を求めよ
 */
List<String> lines = Arrays.asList(
    "application-boot", "access-dispatch:10", "dispatched", "authentication:30", "database-access:45", "response-creation:15", "access-logging"
);

大事なのはとにかく型なので、Generics にビビらずにそこだけはしっかり確認していくよ。

それから、こんな絵が大量に出てくるよ。

legend.png

A -> B fって書いてあるのは java のコードにするとB f(a);ってことで、ABにはStringOptional<Integer>などの具体型が入ります。つまり Generics のことです。

filter

filterの定義はこう。

Stream<T> filter(Predicate<? super T> predicate);

Predicate<T>は「引数がTで戻り値がbool」ってこと、? super Tとかは今は忘れておっけー。

Predicate<T>の型表記をT を bool にするって捉えて、(T -> bool)って書いてみよう。
ついでに Generics も簡略化してしまおう。

Stream<T> filter((T -> bool) f);

filterを使ってlines:のある行だけにするのは、こう書ける。

lines.stream()
        .filter(line -> line.contains(":"))                  // ["access-dispatch:10", "authentication:30", "database-access:45", "response-creation:15"]

定義をT -> boolと書いてみると、実装のline -> line.contains(":")が同じ様に見える。
(このお題では、Tは具体的にはStringってこと)

絵に描くとこんな感じだろうか。

filter.png

filter は変わらなくて、 が変わる変換ってことだ。

map

mapの定義はこう。

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

こっちも同じ様に簡略化すると、こうだ。

Stream<R> map((T -> R) f);

mapを使ってラベル:millisecの行をIntegerにするのは、filterの続きにこう書ける。

lines.stream()
        .filter(line -> line.contains(":"))                  // ["access-dispatch:10", "authentication:30", "database-access:45", "response-creation:15"]
        .map(line -> Integer.valueOf(line.split(":")[1]))    // [10, 30, 45, 15]

ここも定義のT -> Rと実装が同じ形をしている。
(このお題では、Rは具体的にはIntegerってことだから、T -> RString -> Integerに見えていると良い感じ)

絵に描くとこんな感じだろうか。

map.png

map は変わらなくて、 が変わる変換ってことだ。

reduce

reduceの定義は java8 stream には3つあるけど、今から使うのはこうだ。

T reduce(T identity, BinaryOperator<T> accumulator);

BinaryOperator<T>は少し見慣れないけど、「引数がTを2つで戻り値もT」ってことだ。
これもビビらずに簡略化してみよう。

T reduce(T t, ((T, T) -> T) f);

reduceは一般に「畳み込み」と言われていて、リストの先頭から順に結果を蓄積していく処理だ。

reduceを使ってIntegerの行をIntegerの総和にするのは、mapの続きにこう書ける。

lines.stream()
        .filter(line -> line.contains(":"))                  // ["access-dispatch:10", "authentication:30", "database-access:45", "response-creation:15"]
        .map(line -> Integer.valueOf(line.split(":")[1]))    // [10, 30, 45, 15]
        .reduce(0, (acc, n) -> acc + n)                      // 100

定義に少し括弧が多いけど、T, (T, T) -> T0, (acc, n) -> ...が同じ形をしているから少しはわかりやすいはず。

絵に描くとこんな感じだろうか。

reduce with zero a.png

reduceリストの要素単一の結果 になる処理だ。

まとめ

これでクリアー!

 * millisec の総和を求めよ

// ( 再掲 )

lines.stream()
        .filter(line -> line.contains(":"))                  // ["access-dispatch:10", "authentication:30", "database-access:45", "response-creation:15"]
        .map(line -> Integer.valueOf(line.split(":")[1]))    // [10, 30, 45, 15]
        .reduce(0, (acc, n) -> acc + n)                      // 100

で解けた。

一応全体の絵を載せておく。

resolve.png

おわり

ここから先の全てのおまけは以下の記事に分離した。

絵で理解するリスト処理 - java8 stream / javaslang - のおまけ

スッキリ。

18
10
2

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
18
10