Java8で導入されたStreamですが、名前はしっていたものの、使い方がわからず放置していました。
これを機に調べたのですが、コレクションフレームワークの拡張のようですね。
特徴としては以下のようなものがあげられます。
- コレクションの要素に対する変換やフィルタリング、集計といった処理をラムダ式を使って記述できる。
- コレクションに対する操作を並列化できるため、処理を簡単に高速化できる。
Contents
Stream活用法
例えば
name | age | pref |
ichiro | 30 | chiba |
jirou | 18 | tokyo |
saburou | 25 | chiba |
shirou | 45 | kanagawa |
gorou | 9 | tokyo |
のようなデータがList<Map<String,String>>であったとして、
- 展開して全てを表示
- 特定条件のデータだけを表示(例えば20才以上)
- 条件が一致しているものがあるかを判定
- 県別で分類
などを簡単に実現できます。特に「県別で分類」系の作業は自力でやろうとするととても面倒なので助かります。
この手の作業はめちゃくちゃよく出てきますし、自力で実装すると面倒なのとバグがでやすいので、ライブラリがあると非常に便利です。
早く知ってれば良かったです・・・(汗)
実際にコーディング例を紹介します。
展開して全てを表示(forEach)
まずは一番出てくる処理ですね。全体のループ表示です。
lsには上記のテーブルデータが入っています。
1 2 3 4 5 |
//通常の展開(valueはmapの1つ1つのデータ) ls.stream().forEach(value -> { System.out.printf("name: %s :age %s :pref %s", value.get("name"), value.get("age"), value.get("pref")); System.out.print("\n"); }); |
表示
name: ichirou :age 30 :pref chiba
name: jirou :age 18 :pref tokyo
name: saburou :age 25 :pref chiba
name: shirou :age 45 :pref kanagawa
name: gorou :age 9 :pref tokyo
抽出※例えば20才以上のものを抽出(filter)
1 2 3 4 5 |
//20才以上の人間だけをリストアップ Stream<Map<String, String>> smap = ls.stream().filter(value -> Integer.parseInt(value.get("age")) >= 20); smap.forEach(value -> { System.out.printf("name: %s :age %s :pref %s", value.get("name"), value.get("age"), value.get("pref")); }); |
表示
name: ichirou :age 30 :pref chiba
name: saburou :age 25 :pref chiba
name: shirou :age 45 :pref kanagawa
戻り値はStrem<型>ですね。数字系はIntStreamで表現します。(ここでは扱いません。)
ちなみに全て連結して下記のように表示することも可能です。(表示は同じです。)
1 2 3 4 5 6 |
//連結も可能 ls.stream().filter(value -> Integer.parseInt(value.get("age")) >= 20) .forEach(value -> { System.out.printf("name: %s :age %s :pref %s", value.get("name"), value.get("age"), value.get("pref")); System.out.print("\n"); }) |
条件が一致しているものがあるかを判定(anyMatch)
1 2 |
//条件が一致するものが1つでもあればtrueを返す(ここではpref=Tokyoがあるかを判定) boolean isTokyo = ls.stream().anyMatch(value -> value.get("age").equals("tokyo")); |
表示
true
県別で分類(Collectors.groupingBy)
これ手でやると結構大変です。
1 2 3 4 5 6 7 8 |
Map<String, List<Map<String, String>>> collect = ls.stream().collect(Collectors.groupingBy(key -> key.get("pref"))); collect.forEach((key, list) -> { System.out.printf("key: %s \n", key); list.stream().forEach(value -> { System.out.printf("name: %s :age %s ", value.get("name"), value.get("age")); }); System.out.print("\n\n"); }); |
collectですが下記のような構造になっています。
型 Map<String, List<Map<String, String>>>
tokyo => [{pref=tokyo, name=jirou, age=18}, {pref=tokyo, name=gorou, age=9}]
chiba => [{pref=chiba, name=ichirou, age=30}, {pref=chiba, name=saburou, age=25}]
kanagawa => [{pref=kanagawa, name=shirou, age=45}]
表示
key: tokyo
name: jirou :age 18 name: gorou :age 9
key: chiba
name: ichirou :age 30 name: saburou :age 25
key: kanagawa
name: shirou :age 45
ちなみにMapの展開は下記のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
//県別の合計年齢と平均年齢 collect.forEach((key, list) -> { System.out.printf("key: %s \n", key); //ループのたびに初期化 float num = 0; float sumAge = 0; float avAge = 0; // 中から外の変数を変更できないため下記のようなメソッドは無理 // list.forEach(element -> { // sumAge += Integer.parseInt(element.get("age")); // }); //数を算出 num = list.size(); //合計年齢 for (Map<String, String> map : list) { sumAge += Integer.parseInt(map.get("age")); } avAge = (sumAge / num); System.out.printf(" 合計年齢 %.0f \n", sumAge); System.out.printf(" 平均年齢 %.1f \n", avAge); System.out.print("\n"); }); |
表示
key: tokyo
合計年齢 27
平均年齢 13.5
key: chiba
合計年齢 55
平均年齢 27.5
key: kanagawa
合計年齢 45
平均年齢 45.0