不変Map生成は「絶対に変わらない対応表」をコードで保証する技術
不変Map(Immutable Map)は、「一度作ったらキーと値の対応が二度と変わらないMap」です。put も remove も clear もできません。
業務システムでは、次のような「変わったら困る対応表」がたくさんあります。
画面コード → ラベル
ステータスコード → 表示名
権限コード → 説明文
こういったものを「なんとなく HashMap で持つ」と、
どこかで put されてしまい、気づかないうちに中身が変わる危険があります。
不変Map生成は、「ここは絶対に変わらない」という意図を、
コードと型でしっかり表現するための技術です。
Java標準での不変Mapの作り方
Java 9 以降:Map.of(...) で一発生成
Java 9 以降なら、不変Mapは Map.of(...) で簡単に作れます。
import java.util.Map;
public class ImmutableMapSample {
public static void main(String[] args) {
Map<String, String> statusLabels = Map.of(
"NEW", "新規",
"IN_PROGRESS", "処理中",
"DONE", "完了"
);
System.out.println(statusLabels.get("NEW")); // 新規
statusLabels.put("CANCEL", "キャンセル"); // UnsupportedOperationException
}
}
Javaここで押さえておきたい重要ポイントは二つです。
一つ目は、「見た目は普通の Map だが、変更操作はすべて例外になる」ということです。put、remove、clear などはすべて UnsupportedOperationException になります。
二つ目は、「Map.of で作ったMapを定数として公開すると、“変えちゃダメな対応表”だと一目で分かる」ことです。
public final class Statuses {
private Statuses() {}
public static final Map<String, String> LABELS =
Map.of(
"NEW", "新規",
"IN_PROGRESS", "処理中",
"DONE", "完了"
);
}
Javaこのクラスを見た人は、「LABELS は固定のステータス対応表なんだな」とすぐに理解できます。
Java 8 以前:Collections.unmodifiableMap でラップする
Java 8 以前には Map.of がないので、Collections.unmodifiableMap を使って不変Mapを作ります。
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class ImmutableMapLegacy {
public static final Map<String, String> STATUS_LABELS;
static {
Map<String, String> tmp = new HashMap<>();
tmp.put("NEW", "新規");
tmp.put("IN_PROGRESS", "処理中");
tmp.put("DONE", "完了");
STATUS_LABELS = Collections.unmodifiableMap(tmp);
}
public static void main(String[] args) {
System.out.println(STATUS_LABELS.get("NEW")); // 新規
STATUS_LABELS.put("CANCEL", "キャンセル"); // UnsupportedOperationException
}
}
Javaここで深掘りしたいポイントは、「ラップ元のMapを外に出さない」ことです。
unmodifiableMap は「ラップしているMapへの変更を禁止するビュー」を返しているだけなので、
元の tmp への参照をどこかに持っていてそこから put されると、中身は変わってしまいます。
だからこそ、
ラップ元のMapはその場で作って、そのまま unmodifiableMap に渡す
ラップ元のMapへの参照を他に持たない
という書き方がとても重要になります。
不変Mapを返すユーティリティを用意する
「呼び出し側に“変えられない”ことを伝える窓口」
業務メソッドの戻り値として、「不変Mapを返したい」こともよくあります。
そのたびに Collections.unmodifiableMap や Map.copyOf と書くのは少し重いので、
ユーティリティにまとめてしまうとスッキリします。
Java 9 以降なら、Map.copyOf が使えます。
import java.util.Map;
public final class ImmutableMaps {
private ImmutableMaps() {}
public static <K, V> Map<K, V> copyOf(Map<K, V> source) {
if (source == null || source.isEmpty()) {
return Map.of();
}
return Map.copyOf(source); // 不変Mapを返す
}
}
Java使い方はこうです。
Map<String, String> mutable = new java.util.HashMap<>();
mutable.put("A", "あ");
mutable.put("B", "い");
Map<String, String> immutable = ImmutableMaps.copyOf(mutable);
// immutable.put("C", "う"); // UnsupportedOperationException
Javaここでの重要ポイントは、「元のMapとは独立した“不変のコピー”を返している」ことです。
呼び出し側が元のMapをあとで変更しても、
不変Map側は影響を受けません。
Java 8 以前なら、Collections.unmodifiableMap と new HashMap<>(source) を組み合わせます。
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public static <K, V> Map<K, V> copyOfLegacy(Map<K, V> source) {
if (source == null || source.isEmpty()) {
return Collections.emptyMap();
}
return Collections.unmodifiableMap(new HashMap<>(source));
}
Java「不変Mapにしておくと安心な場所」を具体的にイメージする
例1:コード → ラベルのマスタ
画面や帳票で使う「コード → 表示名」の対応表は、
不変Mapにしておくととても安心です。
public final class PaymentMethods {
private PaymentMethods() {}
public static final Map<String, String> LABELS =
Map.of(
"CASH", "現金",
"CARD", "クレジットカード",
"BANK", "銀行振込"
);
}
Javaどこかのコードが LABELS.put("POINT", "ポイント") しようとすると例外になり、
「いつの間にか支払い方法が増えていた」という事故を防げます。
例2:設定値を読み込んだ後の「確定した設定マップ」
設定ファイルやDBから一度だけ読み込んで、
あとは読み取り専用で使うような設定マップも、不変Mapに向いています。
public final class AppConfig {
private final Map<String, String> settings;
public AppConfig(Map<String, String> settings) {
this.settings = Map.copyOf(settings); // 不変コピー
}
public String get(String key) {
return settings.get(key);
}
public Map<String, String> asMap() {
return settings; // すでに不変なのでそのまま返してよい
}
}
Javaここでの重要ポイントは、「コンストラクタで不変コピーを作ってしまう」ことです。
これにより、外から渡されたMapがあとで変更されても、AppConfig の内部状態は変わりません。
「不変Map」と「読み取り専用にしたいだけのMap」を区別する
内部状態を守るための設計
ときどき、
「本当は中身を変えたくないけど、実装の都合で可変Mapを返している」
というコードがあります。
// あまり良くない例
public Map<String, String> getSettings() {
return settings; // 内部の可変Mapをそのまま返している
}
Javaこれだと、呼び出し側が getSettings().put(...) できてしまい、
内部状態がどこからでも書き換えられる危険な設計になります。
「外からは変えてほしくない」と思っているなら、
不変Mapを返すべきです。
public Map<String, String> getSettings() {
return Map.copyOf(settings); // 不変コピーを返す
}
Javaここでの重要ポイントは、「不変Mapは“意図を伝える道具”でもある」ということです。
「このマップは変えちゃダメ」というメッセージを、
型と実装で表現しているわけです。
まとめ:不変Map生成で身につけてほしい感覚
不変Map生成は、単に「変更できないMapを作るテクニック」ではなく、
「ここは絶対に変わってほしくない」という意図をコードに刻むための技術です。
変わらない対応表・マスタ・設定は Map.of(...) や Map.copyOf(...) で不変にする。
Java 8 以前なら Collections.unmodifiableMap(new HashMap<>(source)) で不変ビューを作り、元Mapは外に出さない。
メソッドの戻り値で「外から変更されたくない」ものは、不変Mapを返す。
コンストラクタで不変コピーを作っておくと、外部からの変更の影響を受けない。
もしあなたのコードのどこかに、
public static final Map<String, String> LABELS = new HashMap<>();
Javaのような「定数なのに可変Map」があったら、
それを一度 Map.of(...) や Map.copyOf(...) に置き換えてみてください。
その小さな変更が、
「コレクションの“変わる/変わらない”を意識して設計できるエンジニア」への、
確かな一歩になります。

