エキサイト株式会社の西牧です。
groupingBy を使うとリストをグルーピングできますが、mapping もあわせて使うことでリストに何らかの処理をしつつグルーピングできることを新たに知ったので、その方法を紹介します。
想定ケース
以下のようなリストがあるとします。
@Value class Row { long id; String name; String hobby; } Row row1 = new Row(1, "sato", "music"); Row row2 = new Row(1, "sato", "baseball"); Row row3 = new Row(2, "suzuki", "programming"); Row row4 = new Row(2, "suzuki", "game"); List<Row> rows = List.of(row1, row2, row3, row4);
このリストを UserDetail というクラスのリストに変換する、つまり、User ごとの Hobby リストを作るというのがやりたい処理です。
UserDetail(user=User(id=1, name=sato), hobby=[Hobby(value=music), Hobby(value=baseball)]) UserDetail(user=User(id=2, name=suzuki), hobby=[Hobby(value=programming), Hobby(value=game)])
@Value class User { long id; String name; } @Value class Hobby { String value; } @Value class UserDetail { User user; List<Hobby> hobbies; }
mapping を使わないコード
リストを任意のキーでグルーピングするのは、Stream API の collect
で groupingBy
を使うことでできます。
今回は User
ごとにデータをグルーピングしたいので、User
インスタンスをキーとしました。
groupingBy の第一引数がグルーピングした結果のキーです。
第二引数の LinkedHashMap::new
はグルーピング後のリストの順序を保つためのものです。
第三引数でリストごとにグルーピングすることを指定しています。
本当は Hobby インスタンスのリストを作った上で、それらを User インスタンス でグルーピングしたかったのですが、その 2 つを同時に行う方法がわからず、こういう形になってしまいました。
LinkedHashMap<User, List<Row>> map = rows .stream() .collect(Collectors.groupingBy(e -> new User(e.getId(), e.getName() ), LinkedHashMap::new, Collectors.toList())); List<UserDetail> userDetails = map.keySet() .stream() .map(e -> { List<Row> tmpRows = map.get(e); List<Hobby> hobbies = tmpRows .stream() .map(r -> new Hobby(r.getHobby())) .collect(Collectors.toList()); return new UserDetail(e, hobbies); }).collect(Collectors.toList());
mapping を使ったコード
後ほど上司の方に質問して、groupingBy の第三引数で Collectors.mapping
を使うと、
「Hobby リストを作った上で、それらを User でグルーピングする」ことができるとわかり、修正したものが以下です。
Collectors.mapping(e -> new Hobby(e.getHobby()), Collectors.toList())
の部分で Hobby のリストを作っています。
LinkedHashMap<User, List<Hobby>> map = rows .stream() .collect(Collectors.groupingBy(e -> new User(e.getId(), e.getName()), LinkedHashMap::new, Collectors.mapping(e -> new Hobby(e.getHobby()), Collectors.toList()) )); List<UserDetail> userDetails = map.keySet() .stream() .map(e -> new UserDetail(e, map.get(e))) .collect(Collectors.toList());
おわりに
PHP の連想配列に慣れた身からすると、Java はリストの詰替えがやりづらいなと思っていたのですが、Stream API を使えば簡単にできました。
Stream API はまだ使いこなせていない部分もあるので色々試していきたいです。