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を効率的に勉強する手順を紹介します」リンクから無料で閲覧できるので、是非ご覧ください!

