Javaプログラミングにおいて、キーと値を対にして保持するMapインターフェースは、データの検索や管理を行う上で欠かせないデータ構造です。

長年、JavaにおけるMapの初期化は冗長になりがちでしたが、Java 8からJava 9、そして最新のバージョンへと進化するにつれて、より簡潔で安全な記述が可能になりました。

プロジェクトの要件や使用しているJavaのバージョンによって最適な初期化方法は異なります。

本記事では、古くから使われている基本的な手法から、現在の開発現場で主流となっているモダンな書き方まで、網羅的に詳しく解説します。

この記事を読むことで、コードの可読性を高め、バグの少ない効率的なMapの初期化手法を習得できるでしょう。

Map初期化の基本:Java 8以前のスタンダードな手法

まずは、Java 8以前から使われてきた最も基本的な初期化方法について確認します。

新しいインスタンスを生成し、一つずつ要素を追加していくこの手法は、現在でも「ミュータブル(変更可能)なMap」を動的に作成する場合に広く利用されています。

HashMapを使用した逐次的な初期化

最も一般的な方法は、HashMapをインスタンス化し、putメソッドを使用して要素を一つずつ追加する方法です。

Java 7以降では、右辺の型推論(ダイヤモンド演算子)が導入されたため、以前よりも簡潔に記述できるようになりました。

Java
import java.util.HashMap;
import java.util.Map;

public class MapExample {
    public static void main(String[] args) {
        // インスタンスの生成(ダイヤモンド演算子を使用)
        Map<String, Integer> map = new HashMap<>();

        // 要素の追加
        map.put("Apple", 100);
        map.put("Banana", 200);
        map.put("Orange", 150);

        System.out.println(map);
    }
}
実行結果
{Apple=100, Banana=200, Orange=150}

この方法は、プログラムの実行中に条件に応じて要素を増減させたい場合に適しています

ただし、初期データの定義としては記述量が多くなりがちであるというデメリットがあります。

スタティックイニシャライザによる初期化

クラスの定数としてMapを定義し、プログラムの開始時に初期化したい場合は、staticブロック(スタティックイニシャライザ)を使用します。

Java
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class Config {
    public static final Map<String, String> SETTINGS;

    static {
        Map<String, String> tempMap = new HashMap<>();
        tempMap.put("env", "production");
        tempMap.put("version", "1.0.0");
        // 読み取り専用にする場合はCollections.unmodifiableMapを使用
        SETTINGS = Collections.unmodifiableMap(tempMap);
    }

    public static void main(String[] args) {
        System.out.println(SETTINGS);
    }
}
実行結果
{env=production, version=1.0.0}

不変(イミュータブル)なMapを作成したい場合は、Collections.unmodifiableMapでラップすることが推奨されます。

これにより、意図しない書き換えによるバグを防止できます。

二重ブレース初期化とそのリスク

Java 8以前の環境で「Mapを一行で初期化したい」というニーズに応えるためによく使われていたのが「二重ブレース初期化(Double Brace Initialization)」です。

二重ブレース初期化の構文

Java
import java.util.HashMap;
import java.util.Map;

public class DoubleBraceExample {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>() {{
            put("key1", "value1");
            put("key2", "value2");
        }};
        System.out.println(map);
    }
}

この書き方は一見便利ですが、現代のJava開発では使用を控えるべき手法とされています。

理由は以下の通りです。

匿名内部クラスの生成

この構文は、HashMapを継承した「名前のないクラス」を生成しています。

大量に使用するとクラスファイルが増大し、メモリ消費量が増える原因となります。

メモリリークの懸念

匿名内部クラスは、暗黙的に外部クラスへの参照を保持します。

これにより、外部クラスがガベージコレクション(GC)の対象とならず、メモリリークを引き起こす可能性があります。

シリアライズの問題

継承クラスであるため、標準的なHashMapとしてシリアライズ・デシリアライズする際に問題が発生することがあります。

Java 9以降のモダンな初期化:Map.ofとMap.ofEntries

Java 9からは、待望の静的ファクトリメソッドが導入され、Mapの初期化が劇的に簡単になりました。

これが現代のJavaにおける推奨される書き方です。

Map.ofによる簡潔な初期化

要素数が少ない(10個以下)場合、Map.ofメソッドを使用するのが最もスマートです。

Java
import java.util.Map;

public class MapOfExample {
    public static void main(String[] args) {
        // キーと値を交互に記述する
        Map<String, Integer> map = Map.of(
            "Apple", 100,
            "Banana", 200,
            "Orange", 150
        );

        System.out.println(map);
    }
}
実行結果
{Banana=200, Apple=100, Orange=150}

このメソッドで生成されるMapには、以下のような特徴があります。

不変(イミュータブル)である

生成後にputremoveを呼び出すとUnsupportedOperationExceptionが発生します。

nullを許容しない

キーまたは値にnullを指定するとNullPointerExceptionが発生します。

省メモリ

内部的に最適化された専用のクラスが使用されるため、HashMapよりもメモリ効率が良いです。

Map.ofEntriesによる多要素の初期化

要素数が11個以上の場合は、可変引数を受け取るMap.ofEntriesを使用します。

各要素はMap.entry(key, value)で作成します。

Java
import java.util.Map;
import static java.util.Map.entry;

public class MapOfEntriesExample {
    public static void main(String[] args) {
        Map<Integer, String> map = Map.ofEntries(
            entry(1, "January"),
            entry(2, "February"),
            entry(3, "March"),
            entry(4, "April"),
            entry(5, "May"),
            entry(6, "June"),
            entry(7, "July"),
            entry(8, "August"),
            entry(9, "September"),
            entry(10, "October"),
            entry(11, "November"),
            entry(12, "December")
        );

        System.out.println("Size: " + map.size());
    }
}
実行結果
Size: 12

Map.ofと同様に、生成されるインスタンスは不変です。

不変なMapから可変なMapを作成する

Map.ofなどで作成したMapは非常に便利ですが、後から要素を追加したい場合には不向きです。

その場合は、不変Mapをコンストラクタの引数に渡して、新しい可変Mapを作成するという手法を取ります。

Java
import java.util.HashMap;
import java.util.Map;

public class MutableFromImmutable {
    public static void main(String[] args) {
        // 1. 不変Mapを作成
        Map<String, Integer> immutableMap = Map.of("A", 1, "B", 2);

        // 2. HashMapのコンストラクタに渡して可変Mapを作成
        Map<String, Integer> mutableMap = new HashMap<>(immutableMap);

        // 3. 要素の追加が可能
        mutableMap.put("C", 3);

        System.out.println(mutableMap);
    }
}

このパターンは、デフォルト値を設定したテンプレート用Mapから、個別の処理用Mapを生成する際などに非常によく使われます。

Java 10以降のコピー初期化:Map.copyOf

Java 10では、既存のMapから不変なコピーを作成するためのMap.copyOfが導入されました。

Java
import java.util.HashMap;
import java.util.Map;

public class CopyOfExample {
    public static void main(String[] args) {
        Map<String, String> original = new HashMap<>();
        original.put("Status", "OK");

        // 既存のMapから不変なコピーを作成
        Map<String, String> copy = Map.copyOf(original);

        // originalを変更してもcopyには影響しない
        original.put("Status", "NG");

        System.out.println("Original: " + original);
        System.out.println("Copy: " + copy);
    }
}
実行結果
Original: {Status=NG}
Copy: {Status=OK}

Map.copyOfは、引数として渡されたMapがすでに不変Mapである場合、コピーを行わずにそのまま返すように最適化されています。

メソッドの戻り値として「呼び出し元で変更されたくないMap」を返す際の防御的コピーとして非常に有用です。

Stream APIを活用した動的な初期化

Java 8で導入されたStream APIを使用すると、既存のリストや配列から特定のルールに基づいてMapを生成することができます。

これは、オブジェクトのリストをIDをキーとしたMapに変換する場合などに非常に強力です。

Java
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

class User {
    int id;
    String name;
    User(int id, String name) { this.id = id; this.name = name; }
    public int getId() { return id; }
    public String getName() { return name; }
}

public class StreamMapExample {
    public static void main(String[] args) {
        List<User> users = List.of(
            new User(1, "Alice"),
            new User(2, "Bob"),
            new User(3, "Charlie")
        );

        // ListからMapへの変換
        Map<Integer, String> userMap = users.stream()
            .collect(Collectors.toMap(User::getId, User::getName));

        System.out.println(userMap);
    }
}
実行結果
{1=Alice, 2=Bob, 3=Charlie}

Collectors.toMapを使用する際、キーが重複する可能性がある場合は、マージ関数(第3引数)を指定して衝突回避ルールを定義する必要があります。

初期化方法の比較まとめ

これまで紹介した各手法の特性を表にまとめました。

用途に応じて最適なものを選択してください。

手法導入バージョン変更の可否主な用途
new HashMap<>() + put()全バージョン可変動的に要素を追加する場合
スタティックイニシャライザ全バージョン可変(※1)クラス定数の複雑な初期化
Map.of() / Map.ofEntries()Java 9不変少ない定数データの定義
new HashMap<>(Map.of(...))Java 9可変初期値を持つ可変Mapの生成
Map.copyOf()Java 10不変防御的コピーの作成
Stream APIJava 8可変(※2)コレクションからのデータ変換

(※1) Collections.unmodifiableMap を併用することで不変にできます。

(※2) 使用する Collector によって決まりますが、標準の toMapHashMap 等を返します。

パフォーマンスとメモリに関する注意点

初期化方法を選ぶ際には、パフォーマンスへの影響も考慮する必要があります。

初期容量(Initial Capacity)の指定

大量のデータをHashMapに追加することが分かっている場合、あらかじめサイズを指定することで、内部的な「リハッシュ(メモリ再確保と再配置)」の発生を抑えることができます。

Java
// 100要素入ることが分かっている場合、負荷係数0.75を考慮して少し大きめに確保
Map<String, String> map = new HashMap<>(134);

一方、Map.ofで作成される不変Mapは、要素数に合わせた最小限のメモリフットプリントで構成されるため、定数定義においては非常にメモリ効率が良いというメリットがあります。

Nullの取り扱い

Map.ofやMap.ofEntriesはnullを一切許容しない点に注意してください。

既存のレガシーなコードでnullをキーや値として扱っている場合、そのままMap.ofに置き換えると実行時に例外が発生します。

その場合は引き続きHashMapを使用するか、設計を見直してOptionalなどを活用することを検討しましょう。

まとめ

JavaにおけるMapの初期化は、時代とともに簡潔さと安全性を追求する方向へと進化してきました。

Java 9以降を使用できる環境であれば、不変なMapには「Map.of」シリーズを、可変なMapには「new HashMap<>(Map.of(…))」や「Stream API」を使用するのが現代的なベストプラクティスです。

一方で、二重ブレース初期化のような副作用のある古い手法は避け、可読性と保守性の高いコードを目指すべきです。

データの性質(変更の有無、要素数、nullの可能性)を正しく見極め、適切な初期化方法を選択することで、堅牢なJavaアプリケーションの開発に繋げてください。