Javaで特定のキーごとにグルーピングしたMapを作成する方法を解説します。
例えば社員クラス[Employee]のデータが複数存在するとして、その社員クラスが持つ組織ごとにグルーピングしたMapを作成したいときなどを想定しています。
具体的には以下のような変換をする方法を解説していきます。
List<Employee> → Map<String, Employee>
準備
社員クラスである、Employeeは以下のクラスです。一般的なクラスです。
class Employee { private Employee() { } public Employee(String department, String name, int age) { this.department = department; this.name = name; this.age = age; } private String department; private String name; private int age; public String getDepartment() { return this.department; } public String getName() { return this.name; } public int getAge() { return this.age; } @Override public String toString() { return String.format("組織:%s 名前:%s 年齢:%d歳", this.department, this.name, this.age); } }
これをList<Employee>の形式で使用したのが、以下です。
public class Test { public static void main(String[] args) { List<Employee> employeeList = new ArrayList<>(); Employee employee1 = new Employee("経理部", "山田晃子", 45); Employee employee2 = new Employee("経理部", "須藤珠緒", 69); Employee employee3 = new Employee("営業部", "県直人", 23); employeeList.add(employee1); employeeList.add(employee2); employeeList.add(employee3); employeeList.stream().forEach(System.out::println); } }
結果は以下のようになります。
組織:経理部 名前:山田晃子 年齢:45歳 組織:経理部 名前:須藤珠緒 年齢:69歳 組織:営業部 名前:県直人 年齢:23歳
グルーピングする
例えば、組織ごとにデータを処理したい場合に、組織ごとにデータをグルーピングしたいというケースが存在します。
以下のような結果になると嬉しい場面があります。
経理部: {組織:経理部 名前:山田晃子 年齢:45歳, 組織:経理部 名前:須藤珠緒 年齢:69歳}, 営業部: {組織:営業部 名前:県直人 年齢:23歳}
これを再現するために、Map<String, List<Employee>>の型にデータをグルーピングするというのが本記事の目的です。
Mapのキーには組織、バリューにはキーの組織の社員のListを設定します。
やり方は色々ありますが、今回はスマートなやり方を2パターンご紹介します。
MapのcomputeIfAbsentを使用する
1つ目の方法はMapクラスのcomputeIfAbsentを使用する方法です。
この方法では、for文を使用するため、stream()を何らかの理由で使用できない場合に有効です。※プロジェクトのメンバーがstream()を知らない人が多いなどの理由で使えないことがあるそうです。。。
以下のコードで実現できます。Employeeクラスは同じです。
public class Test { public static void main(String[] args) { // もととなるリストを生成する List<Employee> employeeList = new ArrayList<>(); Employee employee1 = new Employee("経理部", "山田晃子", 45); Employee employee2 = new Employee("経理部", "須藤珠緒", 69); Employee employee3 = new Employee("営業部", "県直人", 23); employeeList.add(employee1); employeeList.add(employee2); employeeList.add(employee3); // パターン① computeIfAbsentを使用する。 Map<String, List<Employee>> groupsOne = new HashMap<>(); for (Employee employee : employeeList) { groupsOne.computeIfAbsent(employee.getDepartment(), (unused) -> new ArrayList<>()).add(employee); } System.out.println(groupsOne); } }
結果は以下のようになります。
{経理部=[組織:経理部 名前:山田晃子 年齢:45歳, 組織:経理部 名前:須藤珠緒 年齢:69歳], 営業部=[組織:営業部 名前:県直人 年齢:23歳]}
期待通りの結果が取得できました。
computeIfAbsentは第1引数の値がキーに存在する場合は、groupsOneからそのキーのバリューを返却します。
キーに存在しない場合は、第1引数の値をキー、第2引数の結果をバリューとしてgroupsOneに追加し、そのバリューを返却します。なお、第2引数がnullである場合は、何も行われません。
以下の流れで処理が実行されます。
- 1つ目のEmployee:groupsOneに経理部が存在しないため、キーが経理部かつバリューが空のリストをgroupsOneに追加する。また、そのバリューに対して、1つ目のemployeeを追加する。
- 2つ目のEmployee:groupsOneに経理部が存在するため、キーが経理部のリストに対して、2つ目のemployeeを追加する。
- 3つ目のEmployee:groupsOneに営業部が存在しないため、キーが営業部かつバリューが空のリストをgroupsOneに追加する。また、そのバリューに対して、3つ目のemployeeを追加する。
そこそこスマートにグルーピングできました。
stream()の終端処理でCollectors.groupingByを使用する
stream()を使用してもいい場合は、終端処理でCollectors.groupingByを使用すると、よりスマートに処理を記述することができます。
以下のコードで実現できます。Employeeクラスは同じです。
public class Test { public static void main(String[] args) { // もととなるリストを生成する List<Employee> employeeList = new ArrayList<>(); Employee employee1 = new Employee("経理部", "山田晃子", 45); Employee employee2 = new Employee("経理部", "須藤珠緒", 69); Employee employee3 = new Employee("営業部", "県直人", 23); employeeList.add(employee1); employeeList.add(employee2); employeeList.add(employee3); // パターン② stream()の終端処理でCollectors.groupingByを使用する。 Map<String, List<Employee>> groupsTwo = employeeList .stream() .collect(Collectors.groupingBy(Employee::getDepartment)); System.out.println(groupsTwo); } }
こちらも結果は同じになります。
{経理部=[組織:経理部 名前:山田晃子 年齢:45歳, 組織:経理部 名前:須藤珠緒 年齢:69歳], 営業部=[組織:営業部 名前:県直人 年齢:23歳]}
ポイントはstreamの終端処理collectでCollectors.groupingByを使用しているところです。
Collectors.groupingByの引数にEmployeeクラスのグルーピングしたい項目(今回は組織)を取得する処理を渡すだけでOKです。
ソースを比較していただければ分かる通り、非常にシンプルでstream()に慣れていれば、可読性も高い記述になっています。
特殊な理由がない限りはstream()を使用してグルーピングすることをオススメします。
まとめ
Javaでデータクラスを特定のキーごとにグルーピングする場合は、以下の方法を使用するといいでしょう。
- MapのcomputeIfAbsentを使用する
- stream()の終端処理でCollectors.groupingByを使用する
どちらも結果は同様になるので、プロジェクトの慣習などに合わせて選択しましょう。
【お知らせ 無料!】未経験エンジニアがJavaでWebサイトを作成できるようになるための学習ロードマップを、無料で公開しています!
実体験に基づいて作成されているので、プログラミングスクールなどで指導されるロードマップにも劣らない品質です。
こちらの「【Java】エンジニア未経験者がJavaを効率的に勉強する手順を紹介します」リンクから無料で閲覧できるので、是非ご覧ください!