「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 にビビらずにそこだけはしっかり確認していくよ。
それから、こんな絵が大量に出てくるよ。
A -> B f
って書いてあるのは java のコードにするとB f(a);
ってことで、A
やB
にはString
やOptional<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
は 型 は変わらなくて、数 が変わる変換ってことだ。
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 -> R
がString -> Integer
に見えていると良い感じ)
絵に描くとこんな感じだろうか。
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) -> T
と0, (acc, n) -> ...
が同じ形をしているから少しはわかりやすいはず。
絵に描くとこんな感じだろうか。
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
で解けた。
一応全体の絵を載せておく。
おわり
ここから先の全てのおまけは以下の記事に分離した。
絵で理解するリスト処理 - java8 stream / javaslang - のおまけ
スッキリ。