Map反転は「矢印の向きをひっくり返す」技
Map<K, V> は「K → V」という矢印の集まりです。
Map反転は、この矢印の向きを「V → K」にひっくり返して、新しい Map<V, K> を作るイメージです。
コード → ラベル(Integer → String)の Map があるとき、
ラベル → コード(String → Integer)の Map も欲しくなることがあります。
この「片方向の対応表から、逆向きの対応表を作る」のが Map反転です。
業務では「マスタの逆引き」「画面表示値からコードを引きたい」といった場面でよく使います。
一番基本:単純な Map を反転する
K → V を V → K にするシンプルな実装
まずは、キーも値もユニークで、重複がない前提の一番シンプルな反転から。
import java.util.HashMap;
import java.util.Map;
public final class Maps {
private Maps() {}
public static <K, V> Map<V, K> invert(Map<K, V> source) {
Map<V, K> result = new HashMap<>();
if (source == null || source.isEmpty()) {
return result;
}
for (Map.Entry<K, V> e : source.entrySet()) {
result.put(e.getValue(), e.getKey());
}
return result;
}
}
Java使い方はこうなります。
Map<Integer, String> codeToLabel = Map.of(
1, "有効",
2, "無効"
);
Map<String, Integer> labelToCode = Maps.invert(codeToLabel);
// "有効" → 1, "無効" → 2 で引ける
System.out.println(labelToCode.get("有効")); // 1
Javaここでの重要ポイントは二つです。
一つ目は、「result.put(e.getValue(), e.getKey()) で“キーと値を入れ替えている”」ことです。K → V を一つずつ見て、V → K として新しい Map に詰め直しています。
二つ目は、「元の Map は一切変更せず、新しい Map を作っている」ことです。
反転は“別の見方の対応表”を作るイメージなので、元の表はそのまま残しておくのが基本です。
重要ポイント:値が重複しているとどうなるか
「値がユニークでない Map は、きれいには反転できない」
ここが Map反転で一番大事なポイントです。
元の Map がこうだったとします。
Map<Integer, String> codeToLabel = Map.of(
1, "有効",
2, "無効",
3, "有効"
);
Java値 "有効" が 1 と 3 の二つのキーに対応しています。
これを単純に反転すると、「"有効" → 1 と "有効" → 3 のどちらを採用するのか?」という問題が発生します。
先ほどの invert 実装だと、後から put した方で上書きされます。
つまり、結果は "有効" → 3 だけが残り、1 は消えます。
業務的にこれは危険です。
「どちらかが勝手に消える」仕様は、バグの温床になります。
だからこそ、Map反転を使うときは必ず次のどちらかを決める必要があります。
値は必ずユニークである(重複しない)と前提にする。
値が重複する場合は、「反転先を Map<V, List<K>> にする」など、複数キーを受け止められる形にする。
安全な反転:値の重複をチェックして例外にする
「値はユニークであるべき」前提をコードに刻む
「この Map は絶対に値が重複しない」という前提で使うなら、
重複を見つけたときに例外を投げる実装にしておくと安全です。
import java.util.HashMap;
import java.util.Map;
public final class Maps {
private Maps() {}
public static <K, V> Map<V, K> invertStrict(Map<K, V> source) {
Map<V, K> result = new HashMap<>();
if (source == null || source.isEmpty()) {
return result;
}
for (Map.Entry<K, V> e : source.entrySet()) {
V value = e.getValue();
if (result.containsKey(value)) {
throw new IllegalArgumentException(
"値が重複しています: " + value);
}
result.put(value, e.getKey());
}
return result;
}
}
Javaここでの重要ポイントは、
「“値はユニークであるべき”という業務ルールを、コードで検証している」ことです。
もしマスタ定義のミスなどで値が重複していたら、
静かに上書きされるのではなく、すぐに例外で気づけます。
「この Map は反転して使う前提だから、値の重複は許さない」という意図を、invertStrict という名前と例外で明確に表現しています。
値が重複する場合の反転:Map<V, List<K>> にする
「同じ値にぶら下がるキーを全部取りたい」パターン
「値が重複することは分かっているし、それを活かしたい」というケースもあります。
たとえば「ステータス → そのステータスを持つユーザーID一覧」のような逆引きです。
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public final class Maps {
private Maps() {}
public static <K, V> Map<V, List<K>> invertToList(Map<K, V> source) {
Map<V, List<K>> result = new HashMap<>();
if (source == null || source.isEmpty()) {
return result;
}
for (Map.Entry<K, V> e : source.entrySet()) {
K key = e.getKey();
V value = e.getValue();
result.computeIfAbsent(value, v -> new ArrayList<>())
.add(key);
}
return result;
}
}
Java使い方の例です。
Map<String, String> userToStatus = Map.of(
"u001", "ACTIVE",
"u002", "INACTIVE",
"u003", "ACTIVE"
);
Map<String, List<String>> statusToUsers =
Maps.invertToList(userToStatus);
// "ACTIVE" → ["u001", "u003"]
// "INACTIVE" → ["u002"]
Javaここで深掘りしたい重要ポイントは三つです。
一つ目は、「反転先の型を Map<V, List<K>> にすることで、“同じ値にぶら下がる複数キー”を全部保持できている」ことです。
二つ目は、「computeIfAbsent を使って、“値ごとのリストを初期化しつつ追加する”処理を簡潔に書いている」ことです。result.computeIfAbsent(value, v -> new ArrayList<>()).add(key); という一行で、「なければ作る+追加する」が表現できています。
三つ目は、「“値が重複することを前提にした反転”と、“値はユニークであるべき反転”を別メソッドに分けている」ことです。
これにより、呼び出し側は用途に応じて明示的に選べます。
Stream を使った Map反転
entrySet → collect の基本パターン
Stream を使うと、Map反転は次のようにも書けます。
import java.util.Map;
import java.util.stream.Collectors;
Map<Integer, String> codeToLabel = Map.of(
1, "有効",
2, "無効"
);
Map<String, Integer> labelToCode =
codeToLabel.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getValue,
Map.Entry::getKey
));
Javaここでの重要ポイントは、
「Collectors.toMap(キー関数, 値関数) に getValue と getKey を渡すことで、“反転”をそのまま表現している」ことです。
ただし、この toMap 版も「値が重複していると例外になる」ことに注意が必要です。
(IllegalStateException: Duplicate key が投げられます)
「値の重複を許容したい」場合は、toMap の第三引数(マージ関数)を指定するか、
先ほどの invertToList のように Map<V, List<K>> を自前で組み立てる方が分かりやすいです。
まとめ:Map反転ユーティリティで身につけてほしい感覚
Map反転は、
単に「キーと値を入れ替えるテクニック」ではなく、
「片方向の対応表を、逆方向からも引けるように設計する技術」です。
invert のような単純反転は、「値がユニークである」前提がないと危険になる。
「値はユニークであるべき」なら、invertStrict のように重複検出して例外にする。
「値の重複を活かしたい」なら、Map<V, List<K>> に反転する invertToList を使う。
Stream では entrySet().stream() → Collectors.toMap(getValue, getKey) という形で反転を表現できるが、重複時の挙動を意識する。
あなたのコードのどこかに、
「手作業で“ラベル → コード”の Map をもう一つ書いている」箇所があれば、
それを一度「元の Map からの反転ユーティリティ」に置き換えられないか眺めてみてください。
その小さな整理が、
「対応表を一元管理しつつ、両方向からスマートに引けるエンジニア」への、
確かな一歩になります。
