JavaのgroupingByを完全ガイド!ラムダ式で集計・グルーピング・重複削除をマスターしよう
生徒
「Javaのラムダ式でデータを集計したり、グループ分けしたりする方法ってありますか?」
先生
「はい、ありますよ。Collectors.groupingByを使えば、カテゴリごとに集計したり、特定のキーでグルーピングしたりできます。」
生徒
「具体的にどうやって使うんですか?重複をなくしたりもできますか?」
先生
「もちろんできますよ。それではgroupingByの使い方をわかりやすく説明していきましょう。」
1. groupingByとは?Javaの集計や分類に便利なメソッド
JavaのgroupingByメソッドは、Stream APIと一緒に使ってデータをグループ化するためのメソッドです。特に、ラムダ式を使って条件に応じてリストを分類したいときに便利です。
たとえば、商品のリストをカテゴリ別に分けたい場合や、名前の頭文字でグループに分けたいときに使えます。
2. groupingByの基本的な使い方:カテゴリで分類
まずは基本の使い方を紹介します。ここでは、商品のカテゴリでグループ分けする例を見てみましょう。
import java.util.*;
import java.util.stream.*;
import static java.util.stream.Collectors.*;
public class GroupingExample {
public static void main(String[] args) {
List<Product> products = Arrays.asList(
new Product("りんご", "果物"),
new Product("バナナ", "果物"),
new Product("にんじん", "野菜"),
new Product("トマト", "野菜")
);
Map<String, List<Product>> grouped = products.stream()
.collect(groupingBy(Product::getCategory));
grouped.forEach((category, list) -> {
System.out.println(category);
list.forEach(p -> System.out.println(" - " + p.getName()));
});
}
}
class Product {
private String name;
private String category;
public Product(String name, String category) {
this.name = name;
this.category = category;
}
public String getName() { return name; }
public String getCategory() { return category; }
}
この例では、getCategory()をキーにして、同じカテゴリのProductがリストにまとめられます。
3. groupingByで集計処理:数をカウントする
groupingByは、単にグループ分けするだけでなく、集計処理も同時に行えます。たとえば、各カテゴリに属する商品の数を数えるには次のようにします。
Map<String, Long> counts = products.stream()
.collect(groupingBy(Product::getCategory, counting()));
このコードでは、各カテゴリに何個の商品があるかをMapにまとめています。
4. groupingByとmappingの組み合わせで必要な情報だけ抽出
groupingByにmappingを組み合わせると、必要なデータだけを抽出してグループにまとめることができます。以下の例では、カテゴリごとに商品名だけをリストにしています。
Map<String, List<String>> groupedNames = products.stream()
.collect(groupingBy(Product::getCategory, mapping(Product::getName, toList())));
Productオブジェクトのままではなく、getName()で名前だけをリストにしたいときに便利です。
5. groupingByとSetで重複を排除する
groupingByを使うと、重複のないリストを作ることも可能です。toSet()を使えば、同じ値は自動的に1つにまとめられます。
Map<String, Set<String>> groupedSet = products.stream()
.collect(groupingBy(Product::getCategory, mapping(Product::getName, toSet())));
重複を除きつつ、カテゴリごとの商品名リストを取得できます。
6. groupingByの中でソートされた順で格納する方法
groupingByはデフォルトでHashMapを使いますが、キーの順番を保持したいときはLinkedHashMapを明示的に指定することで並び順を維持できます。
Map<String, List<Product>> ordered = products.stream()
.collect(groupingBy(
Product::getCategory,
LinkedHashMap::new,
toList()
));
データを順番通りに保持したい場面ではLinkedHashMapを活用しましょう。
7. groupingByを使ってネストされたマップを作る
groupingByは入れ子構造のグルーピングも可能です。たとえば、カテゴリ→文字数別のグループといった多階層の分類もできます。
Map<String, Map<Integer, List<String>>> nested = products.stream()
.collect(groupingBy(
Product::getCategory,
groupingBy(p -> p.getName().length(), mapping(Product::getName, toList()))
));
このように、複雑なグループ分けにもgroupingByは柔軟に対応できます。
8. ラムダ式とgroupingByの相性の良さとは?
Javaのラムダ式は、groupingByとの相性が抜群です。データ加工や分類を直感的に記述できるため、for文よりも簡潔で見やすくなります。特にCollectorsと一緒に使うことで、複雑な集計処理も1行で表現できます。
たとえば、ユーザーの年齢でグループ分けしたり、ログデータを日付単位でまとめたりといった操作もラムダ式でスッキリ書けるようになります。
まとめ
ここまでJavaのgroupingByについて、グルーピング、集計、重複削除、ネスト構造の分類など、多様な活用方法を具体例とともに丁寧に整理してきました。ラムダ式とStream APIを組み合わせることで、複雑な処理でも直感的かつ簡潔に記述できる点は、実践的な開発において大きな利点となります。特に、カテゴリ別の集計や文字列長による分類、必要な情報だけを抽出するmappingとの組み合わせは、多くの業務アプリケーションでも即戦力として使える場面が多く、汎用性の高いテクニックだといえます。また、重複削除を行いたい場合にはtoSet()を利用し、順序を保持したい状況にはLinkedHashMapを指定することで柔軟に対応できます。これらの機能はデータの分析や可視化などにも応用でき、より効率的な処理を実現します。
さらに、ネストされたマップ構造を扱うことで、階層的なデータ分類にも対応できるようになり、より高度なロジックを組むことが可能です。複数条件を組み合わせた分類を行う場合でも、groupingByを入れ子にして記述するだけで自然に構造化されたデータを得られるため、データ処理における表現力は非常に高くなります。このような柔軟性は、ログ解析や統計処理、商品管理システムなど、さまざまなシーンで応用できる重要な基礎技術です。
また、ラムダ式とgroupingByの相性の良さから、従来のfor文では煩雑になりやすい処理も、短くわかりやすいコードに落とし込むことができ、保守性の向上にもつながります。特に、読みやすさと変更しやすさは現場でも求められるポイントであり、groupingByをしっかりと理解することはJavaプログラミングを一段階上達させる大切なステップといえるでしょう。
サンプルプログラムまとめ
以下はgroupingByの総合的な使い方をまとめたサンプルです。
import java.util.*;
import java.util.stream.*;
import static java.util.stream.Collectors.*;
public class GroupingSummary {
public static void main(String[] args) {
List<Product> products = Arrays.asList(
new Product("りんご", "果物"),
new Product("バナナ", "果物"),
new Product("にんじん", "野菜"),
new Product("トマト", "野菜"),
new Product("りんご", "果物")
);
Map<String, Long> counts = products.stream()
.collect(groupingBy(Product::getCategory, counting()));
Map<String, Set<String>> uniqueNames = products.stream()
.collect(groupingBy(Product::getCategory, mapping(Product::getName, toSet())));
Map<String, Map<Integer, List<String>>> nested = products.stream()
.collect(groupingBy(
Product::getCategory,
groupingBy(p -> p.getName().length(), mapping(Product::getName, toList()))
));
System.out.println(counts);
System.out.println(uniqueNames);
System.out.println(nested);
}
}
class Product {
private String name;
private String category;
public Product(String name, String category) {
this.name = name;
this.category = category;
}
public String getName() { return name; }
public String getCategory() { return category; }
}
生徒:「groupingByって、最初は難しそうだと思っていましたが、使い方がわかるととても便利ですね。」
先生:「そうですね。特にラムダ式と組み合わせることで処理がシンプルになりますし、分類や集計の幅が大きく広がります。」
生徒:「mappingを使って必要な情報だけ取り出せるところもすごく役に立ちそうです。」
先生:「はい。実務でも特定の項目だけ抽出したい場面はよくありますし、toSet()で重複削除を行うのも大切なテクニックです。」
生徒:「ネストされたグルーピングができるのも意外でした。分類の幅がさらに広がりますね。」
先生:「その通りです。階層的な分類はデータ分析の基本でもあるので、ぜひ実践でも活用してみてください。」
生徒:「今日の内容をもっと練習して、しっかり身につけたいと思います!」
先生:「ぜひ頑張ってください。groupingByを理解すればJavaのデータ処理がもっと楽しくなりますよ。」