Java 8 でグルーピング処理 - List<V> を Map<K, V> へ変換
Java 8 で List<V>
を Map<K, V>
へ変換するようなグルーピング処理をいくつか試してみました。
ソースは http://github.com/fits/try_samples/tree/master/blog/20150420/
はじめに
今回は、下記をリスト化した List<Data>
を id
でグルーピングして Map<String, Data>
へ変換します。
class Data { private String id; private String name; public Data(String id, String name) { this.id = id; this.name = name; } public String getId() { return id; } ・・・ }
Java 8 より前のバージョンでは以下のようにすると思います。
拡張 for 利用
List<Data> dataList = Arrays.asList( new Data("d1", "sample1"), new Data("d2", "sample2"), new Data("d3", "sample3") ); Map<String, Data> res = new HashMap<>(); for (Data d : dataList) { res.put(d.getId(), d); }
また、Java 8 で Map<String, List<Data>>
へ変換するなら Collectors.groupingBy
を使うだけです。
groupingBy で Map<String, List > へ変換
Map<String, List<Data>> res = dataList.stream().collect( Collectors.groupingBy(Data::getId) );
(1) forEach
まずは、拡張 for の変わりに forEach
メソッドを使用する方法です。
Map<String, Data> res = new HashMap<>(); dataList.forEach(d -> res.put(d.getId(), d));
(2) toMap
次は、Collectors.toMap
を使用する方法です。
toMap の 2引数版
Map<String, Data> res = dataList.stream().collect(
Collectors.toMap(Data::getId, d -> d)
);
もしくは
Map<String, Data> res = dataList.stream().collect( Collectors.toMap(Data::getId, UnaryOperator.identity()) );
ここで、2引数版の toMap メソッドには以下のような注意点があります。
- 同一キーを持つオブジェクトを複数含んでいると
IllegalStateException
を throw する
例えば、以下は IllegalStateException となります。
IllegalStateException が発生するコード例
List<Data> dataList2 = Arrays.asList( new Data("d1", "sample1"), new Data("d2", "sample2"), new Data("d3", "sample3"), new Data("d1", "sample1-b") // d1 が重複 ); // IllegalStateException: Duplicate key Data(d1, sample1) が発生 Map<String, Data> res = dataList2.stream().collect( Collectors.toMap(Data::getId, d -> d) );
IllegalStateException エラー内容
Exception in thread "main" java.lang.IllegalStateException: Duplicate key Data(d1, sample1)
IllegalStateException を発生させないようにするには、3引数版の toMap を使います。
toMap の 3引数版
第 3引数で同一キーの値が複数あった場合にどちらを選択するかを指定します。
最初の要素を採用する場合
// 結果 [ d1: Data(d1, sample1), d2: Data(d2, sample2), d3: Data(d3, sample3) ] Map<String, Data> res = dataList2.stream().collect( Collectors.toMap(Data::getId, d -> d, (d1, d2) -> d1) );
最後の要素を採用する場合
// 結果 [ d1: Data(d1, sample1-b), d2: Data(d2, sample2), d3: Data(d3, sample3) ] Map<String, Data> res = dataList2.stream().collect( Collectors.toMap(Data::getId, d -> d, (d1, d2) -> d2) );
(3) groupingBy + collectingAndThen
あまり実用的では無いと思いますが、groupingBy と collectingAndThen を組み合わせる方法も考えられます。
// 結果 [ d1: Data(d1, sample1), d2: Data(d2, sample2), d3: Data(d3, sample3) ] Map<String, Data> res = dataList2.stream().collect( Collectors.groupingBy( Data::getId, Collectors.collectingAndThen( Collectors.toList(), a -> a.get(0) //最初の要素を採用 ) ) );
toList を minBy 等で代用する事も可能です。
Map<String, Data> res = dataList2.stream().collect( Collectors.groupingBy( Data::getId, Collectors.collectingAndThen( Collectors.minBy((a, b) -> 0), a -> a.get() ) ) );
(4) collect の 3引数版
最後に、3引数版の collect を使う方法です。
パラレル実行で使用する第 3引数が必須となっている点が微妙だと思います。 (下記 Map::putAll の箇所を null にすると NullPointerException となります)
Map<String, Data> res = dataList.stream().collect( HashMap::new, (m, d) -> m.put(d.getId(), d), Map::putAll );