Javaで「要素の出現回数」を集計したい場面はよくあります。たとえば文字列の頻度やイベントの回数を数える場合、通常は Map<String, Integer>
を使って自前でカウント処理を書くことになります。しかし、この方法は初期化や存在チェック、加算処理などが煩雑です。
そんな手間を省き、要素の出現回数を簡潔かつ安全に管理できるのが、GoogleのGuavaライブラリが提供するMultiset
です。
この記事では、Multiset
の基本的な使い方から、具体的なユースケース、代表的な実装クラスの特徴、注意点までを丁寧に解説します。
Multisetとは?Java標準Setとの違い
Java標準ライブラリのSet
は重複を許さないコレクションです。同じ要素を複数回追加しても、内部的には1つの要素として管理されます。
Set<String> standardSet = new HashSet<>();
standardSet.add("apple");
standardSet.add("apple");
System.out.println(standardSet.size()); // 出力: 1
一方、GuavaのMultiset
は同じ要素を複数回保持できるコレクションです。内部的には「要素 → 出現回数」の形式で管理され、要素の個数を明示的に取得・制御できます。
Multiset<String> multiset = HashMultiset.create();
multiset.add("apple");
multiset.add("apple");
System.out.println(multiset.count("apple")); // 出力: 2
System.out.println(multiset.size()); // 出力: 2
このように、Multiset
を使えば「同じ値がいくつあるのか?」を簡単かつ直感的に扱うことができ、Set
や Map
では煩雑だった頻度集計処理を大幅に簡略化できます。
Multisetの実装クラスと特徴
Guava には用途に応じて複数の Multiset
実装が用意されています。それぞれの特徴を理解して最適な実装を選ぶことで、パフォーマンスや可読性を最適化できます。
実装クラス | 特徴 |
---|---|
HashMultiset | 最も基本的な実装。順序なしで高速。標準的な用途におすすめ。 |
LinkedHashMultiset | 要素の追加順を保持。順序を保ちたいときに便利。 |
TreeMultiset | 自然順または Comparator による順序を保持。ソートされた出力に最適。 |
EnumMultiset | enum型専用。非常に軽量で高速な実装。EnumSet のような感覚で使える。 |
ConcurrentHashMultiset | スレッドセーフな実装。複数スレッドから安全にアクセス可能。 |
ImmutableMultiset | 不変(イミュータブル)な実装。変更不要な集計結果などに最適。 |
ImmutableSortedMultiset | 不変かつ順序付き。読み取り専用のソート済みカウントデータに最適。 |
補足解説:
EnumMultiset
は定義済みの列挙型に特化しており、要素数が限られるケースで非常に効率的です。ConcurrentHashMultiset
はスレッドセーフな実装としてAtomicInteger
を内部的に活用しており、高頻度な集計処理でも安全に使えます。ImmutableMultiset
およびImmutableSortedMultiset
は変更不可であることから、外部公開するデータ構造としても信頼性が高く、予期せぬ変更を防げます。
このように、目的に応じて最適な Multiset
実装を選べば、より安全で効率的なコレクション操作が可能になります。
Multisetの基本的な使い方
Multiset
は、要素の追加・削除・カウント取得といった操作を直感的なメソッドで行えます。
ここでは代表的なメソッドを使って、基本的な使い方を紹介します。
Multiset<String> ms = HashMultiset.create();
// 要素の追加(1つずつ)
ms.add("banana");
// 要素の追加(複数個)
ms.add("banana", 2); // banana を合計3個に
// カウントの取得
System.out.println(ms.count("banana")); // 出力: 3
// 要素の削除(1つだけ)
ms.remove("banana"); // banana のカウント: 2
// 要素の削除(指定数だけ)
ms.remove("banana", 2); // banana が完全に削除される
// 重複を除いた要素一覧(Set と同様)
System.out.println(ms.elementSet()); // [](Set<String> として出力)
// 全要素の合計数(重複込み)
System.out.println(ms.size()); // 0(banana は削除済み)
主な操作メソッドのまとめ
メソッド | 説明 |
---|---|
add(E) / add(E, int) | 要素を1つまたは複数回追加 |
remove(E) / remove(E, int) | 要素を1つまたは指定数だけ削除 |
count(E) | 指定要素の現在のカウントを取得 |
elementSet() | 重複を除いた要素の集合(Set<E> )を返す |
size() | 要素の合計数(重複込み)を返す |
setCount(E, int) | 要素のカウントを任意の値に直接設定 |
clear() | すべての要素を削除 |
Multiset
は、こうした集計処理を非常に簡潔に書けるのが大きな利点です。
手動で Map
を使ってカウント処理を組むよりも、明快で安全性の高い実装ができます。
使用例:文字列配列から頻度集計する
Multiset
は、配列やリスト内の要素がそれぞれ何回登場したかをカウントする処理に非常に適しています。たとえば、文章中の単語の出現頻度を集計したい場合、以下のように簡潔に書けます。
String[] words = { "apple", "banana", "apple", "orange", "banana", "apple" };
// 配列から Multiset を作成
Multiset<String> counter = HashMultiset.create(Arrays.asList(words));
// 各要素の出現回数を出力
for (Multiset.Entry<String> entry : counter.entrySet()) {
System.out.printf("%s: %d 回%n", entry.getElement(), entry.getCount());
}
実行結果(例):
banana: 2 回
orange: 1 回
apple: 3 回
このような頻度集計を Map<String, Integer>
で実装しようとすると、初期化や存在確認、インクリメント処理が必要になり、冗長になりがちです。
一方、Multiset
を使えば、初期化不要・例外処理不要・1行で集計可能と、コードが驚くほど簡潔になります。
ログ集計・タグ解析・投票集計など、「値の回数を数える」あらゆるシーンで役立つ構文です。
よくある疑問Q&A
Map<String, Integer>
と何が違うの?-
Map
を使う場合、要素の存在チェックや初期値の設定、加算処理を毎回自分で書く必要があります。一方Multiset
はこれらの処理を内部で抽象化しており、add()
やcount()
を呼ぶだけで安全かつ簡潔に頻度管理ができます。// Map は毎回 getOrDefault で初期化+加算が必要 map.put(word, map.getOrDefault(word, 0) + 1); // Multiset は add だけで完結 multiset.add(word); // Multisetならこれだけ
- null は扱える?
-
HashMultiset
とLinkedHashMultiset
のみ、null
を要素として扱うことができます。他の実装ではnull
は使用できません。 - パフォーマンスはどう?
-
Multiset
は内部的に効率的なデータ構造を使用しているため、手動でMapを実装するよりも高速です。特にEnumMultiset
は特定のケースで非常に高いパフォーマンスを発揮します。
まとめ:頻度集計にはMultisetが最適解
Guava の Multiset
を使えば、要素の出現回数を簡潔・安全・効率的に管理できます。Map<String, Integer>
のように毎回初期化や存在チェックを書く必要はなく、add()
や count()
といった直感的なメソッドで扱えるのが大きな魅力です。
また、用途に応じた豊富な実装(順序付き・ソート済み・スレッドセーフ・イミュータブルなど)が用意されており、単なる集計処理だけでなく、実用的なアプリケーションの構築にも柔軟に対応できます。
文字列の頻度分析、ログ集計、タグ出現回数の測定、投票結果の集計など、「値が何回出たか?」を扱う処理には Multiset
が最適です。ぜひ、日々のJava開発に取り入れてみてください。