【Guava】BiMapで双方向マッピングを実現する方法

Javaでデータのマッピングを行う際、通常はMapを使ってキーから値を取得します。しかし、「値からキーを逆引きしたい」という場面に遭遇したことはありませんか?

そんな課題を解決するのが、GoogleのGuavaライブラリが提供するBiMap(双方向マップ)です。この記事では、BiMapの基本概念から実践的な活用方法まで、分かりやすく解説していきます。

目次

BiMapとは?従来のMapとの根本的な違い

BiMap<K, V>は、キーと値のどちらも重複を許さないという特徴を持つ、双方向対応のマップです。
通常のMapでは、同じ値を複数のキーに対応させることができますが、BiMapではそれが許されません。つまり、キー・値ともに一意性が保証されるのが大きな違いです。

この性質により、BiMapを使うと次のような操作が可能になります。

  • キー → 値:通常のMapと同様のアクセス
  • 値 → キーinverse()メソッドで逆引きが可能

Mapとの違い(比較表)

特徴MapBiMap
キーの一意性必須必須
値の一意性任意必須
値からキーの検索困難簡単
双方向アクセス不可可能

このように、BiMapは「値からも逆にたどれる」構造を自然に実現できるため、IDと名称の相互変換コードとラベルの対応表など、一貫性のある双方向マッピングが求められる場面に非常に適しています。

BiMapの実装クラス

Guavaライブラリでは、用途に応じた複数のBiMap実装クラスが提供されています。目的に合ったものを選ぶことで、より効率的で安全な設計が可能です。

クラス名特徴と主な用途
HashBiMap最も一般的な実装。高速かつ可変。標準的な用途で迷ったらこれを使えばOKです。
ImmutableBiMap一度作ったら変更できない不変のBiMap。スレッドセーフで、設定定数や定義情報に最適。
EnumBiMapキーと値がどちらもenumの場合に特化。メモリ効率とパフォーマンスに優れます。
EnumHashBiMapキーのみがenumで、値は任意の型にしたい場合に使います。

各実装は用途や制約が異なるため、「可変か不変か」「enumを使うかどうか」などを基準に選ぶとよいでしょう。

BiMapの基本的な使い方(コード例)

ここでは、GuavaのBiMapを使った基本的な操作を、コード例を交えて順を追って紹介します。

1. BiMapの生成と値の登録

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

BiMap<String, String> biMap = HashBiMap.create();
biMap.put("apple", "red");
biMap.put("banana", "yellow");

通常のMapと同様にput()で要素を追加できます。

2. 値からキーを取得する(inverse)

String fruit = biMap.inverse().get("red");  // apple

inverse()メソッドを使えば、値から対応するキーを取得できます。inverse()は常に元のBiMapと同期されており、一方を変更すればもう一方にも反映されます。

3. 値が重複した場合の挙動

値が重複するとIllegalArgumentExceptionが発生します。

biMap.put("grape", "red"); // redは既に使用されている → 例外

BiMapでは値の一意性も必須なため、すでに使われている値を登録しようとするとIllegalArgumentExceptionが発生します。

4. 重複を許容したい場合:forcePut()

biMap.forcePut("grape", "red");
// この場合 "apple" → "red" のマッピングは削除される

どうしても既存の値と同じ値を使いたい場合は、forcePut()を使うことで既存のマッピングを自動的に上書きできます。


このように、BiMapは通常のMapに加えて逆引きや一意性の制約を提供しつつ、直感的なAPIで使える強力なツールです。

BiMapの使用上の注意点とベストプラクティス

BiMapは非常に便利な双方向マップですが、使い方を誤ると予期せぬバグを招く可能性があります。ここでは、安全かつ効果的に使うための注意点とベストプラクティスを紹介します。

値の一意性を常に意識する

BiMapでは、キーだけでなく値も一意である必要があります。通常のMapのように「複数のキーに同じ値を割り当てる」ことはできません。
そのため、put()を使う際には、対象の値が既に使われていないかを事前に確認するか、次項のforcePut()の使用を検討しましょう。

inverse()は常に同期されるビュー

inverse()で取得したマップは、元のBiMapと双方向にリンクされた「ライブビュー」です。どちらかを変更すると、もう一方にも即座に反映されます。

BiMap<String, String> biMap = HashBiMap.create();
biMap.put("A", "1");

BiMap<String, String> inverse = biMap.inverse();
inverse.put("2", "B");

System.out.println(biMap.get("B")); // 結果: 2

このような動作は便利な反面、どちらのマップを操作しても元のデータが変わるため、可読性や管理に注意が必要です。

forcePut()は慎重に使う

forcePut()を使うと、既に登録されている値を自動的に上書きして新しいマッピングを作成します。
便利ではありますが、古いマッピングが失われるため、想定外のデータ消失につながる可能性があります。

biMap.put("x", "100");
biMap.forcePut("y", "100"); // "x" → "100" のマッピングが削除される

forcePut()は明示的に競合を検知してから使うことを推奨します。


BiMapは非常にパワフルなツールですが、その分扱いも慎重さが求められます。ルールを理解した上で活用することで、より堅牢で見通しのよいコードを実現できます。

実践的なユースケース

BiMapは、キーと値をどちらからも参照したいというニーズがある場面で特に威力を発揮します。以下に、実際の開発で役立つ代表的なユースケースを紹介します。

ユーザIDとユーザ名の双方向マッピング

データベースではユーザIDで管理しつつ、画面表示ではユーザ名を使いたい場合に便利です。IDと名前を相互に変換できます。

BiMap<String, String> userMap = HashBiMap.create();
userMap.put("U001", "Alice");
userMap.put("U002", "Bob");
userMap.put("U003", "Carol");

String name = userMap.get("U001");           // → Alice
String id = userMap.inverse().get("Carol");  // → U003

ファイル種別(enum)とアイコン(enum)の対応

アプリケーションでファイルタイプごとに表示アイコンを切り替えたい場合、EnumBiMapを使えば効率的にマッピングできます。

BiMap<FileType, IconType> iconMap = EnumBiMap.create(FileType.class, IconType.class);
iconMap.put(FileType.TEXT, IconType.DOCUMENT_ICON);
iconMap.put(FileType.IMAGE, IconType.PHOTO_ICON);
iconMap.put(FileType.AUDIO, IconType.SPEAKER_ICON);

HTTPステータスコードとその説明文の相互変換

ログやUI表示などで、ステータスコードと説明を柔軟に行き来したい場合に役立ちます。EnumHashBiMapなら、キーがenum、値が任意型の組み合わせに対応できます。

BiMap<HttpStatus, String> statusMap = EnumHashBiMap.create(HttpStatus.class);
statusMap.put(HttpStatus.OK, "成功");
statusMap.put(HttpStatus.BAD_REQUEST, "不正なリクエスト");
statusMap.put(HttpStatus.FORBIDDEN, "アクセス拒否");

このように、BiMapは「一貫性のある双方向マッピング」が求められるあらゆる場面で応用可能です。値からキーを簡単に取得できるという特性を活かすことで、コードの簡潔性・保守性が大きく向上します。

ImmutableBiMapで不変マップを作る

変更されることのない固定データを扱う場面では、通常のBiMapよりも変更不可なImmutableBiMapを使うのが安全です。
GuavaのImmutableBiMapを使えば、スレッドセーフかつ再利用しやすい
双方向マップを簡単に作成できます。

作成例

import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;

BiMap<String, String> imMap = ImmutableBiMap.of(
    "one", "1",
    "two", "2"
);

System.out.println(imMap.get("two"));           // → 2
System.out.println(imMap.inverse().get("1"));   // → one

主なメリット

  • 変更不可:誤って更新される心配がなく、安全に他クラスと共有可能
  • スレッドセーフ:同期処理不要で複数スレッドから安全に利用できる
  • 読み取り専用設計に最適:設定値や定数、固定マッピングに最適

注意点

  • 一度作成した後に要素を追加・削除することはできません。動的なデータには不向きです。
  • 複数要素をまとめて登録したい場合は、ImmutableBiMap.Builderの使用も検討しましょう。

ImmutableBiMap.Builderでの作成例

BiMap<String, String> map = ImmutableBiMap.<String, String>builder()
        .put("A", "α")
        .put("B", "β")
        .build();

まとめ:BiMapで効率的な双方向マッピングを実現

GuavaのBiMapは、キーと値の両方に一意性を求めるマッピングにおいて非常に便利なデータ構造です。特に、値からキーへの逆引きが必要な場面では、標準のMapよりも圧倒的に扱いやすくなります。

✅ BiMapの主な利点

  • キーだけでなく値にも一意性を保証
  • inverse()値からキーへのアクセスが可能
  • forcePut()で値の競合を強制的に上書きできる(※注意して使用)
  • EnumBiMapImmutableBiMapなど、用途に応じた実装が充実

✅ こんなときにBiMapが活躍

  • ユーザID ↔ ユーザ名 のような相互変換が必要なケース
  • ファイル種別 ↔ アイコン など、意味の対応付け
  • ステータスコードと説明文など、一貫性のある固定マッピング

双方向のマッピングが必要な場面では、従来のMapに頼るよりも、BiMapを導入することで可読性・保守性・安全性が大きく向上します。Guavaライブラリの導入を検討し、より洗練されたJavaアプリケーションを構築してみてください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次