CodeGym /コース /JAVA 25 SELF /Map.copyOf、Set.copyOf とその他のユーティリティ

Map.copyOf、Set.copyOf とその他のユーティリティ

JAVA 25 SELF
レベル 34, レッスン 1
使用可能

1. copyOf メソッド: List.copyOf、Set.copyOf、Map.copyOf

次のような状況を想像してください。リスト、セット、またはマップを返すメソッドがあり、呼び出し側のコードがそのコレクションの中身を一切変更できないことが重要です。たとえば「サポートする通貨のリスト」や「ユーザーのロール集合」などです。うっかりした追加/削除がビジネスロジックを壊してしまう可能性があります。

Java 10 以前は、手動でコレクションのコピーを返すか、Collections.unmodifiableList(list) のようなラッパーを使うしかありませんでした。ラッパーは自分経由の変更を防ぐだけで、元のコレクションの変更までは防げません。

今はより確実な方法 — copyOf メソッドがあります。

これは何か?

List.copyOf(Collection)、Set.copyOf(Collection)、Map.copyOf(Map)(Java 10)は、渡されたコレクションまたはマップの本物の不変コピーを作成します。

  • コピーは元のコレクションと無関係です。オリジナルを変更しても、copyOf の結果には影響しません。
  • コピーは変更できません。あらゆる変更操作は UnsupportedOperationException を投げます。

不変コピーの作成例

import java.util.*;

public class CopyOfDemo {
    public static void main(String[] args) {
        List<String> modifiable = new ArrayList<>();
        modifiable.add("Java");
        modifiable.add("Python");

        // 不変のコピーを作成する
        List<String> immutable = List.copyOf(modifiable);

        // コピーを変更してみる
        try {
            immutable.add("C++"); // UnsupportedOperationException を送出する
        } catch (UnsupportedOperationException e) {
            System.out.println("不変コレクションに要素を追加することはできません!");
        }

        // 元のリストを変更する
        modifiable.add("Kotlin");

        // コピーは不変のまま!
        System.out.println("元のリスト: " + modifiable);
        System.out.println("不変のコピー: " + immutable);
    }
}

プログラムの出力:

不変コレクションに要素を追加することはできません!
元のリスト: [Java, Python, Kotlin]
不変のコピー: [Java, Python]

各メソッドの概要

  • List.copyOf(Collection) — 元のコレクションの要素を持つ不変リストを返します。
  • Set.copyOf(Collection) — コレクションの要素を持つ不変セットを返します。重複は取り除かれます。
  • Map.copyOf(Map) — 元のマップのエントリを持つ不変マップを返します。

Set と Map の例

import java.util.*;

public class CopyOfSetMapDemo {
    public static void main(String[] args) {
        Set<String> modifiableSet = new HashSet<>(Set.of("A", "B", "C"));
        Set<String> immutableSet = Set.copyOf(modifiableSet);

        // 要素を追加してみる
        try {
            immutableSet.add("D");
        } catch (UnsupportedOperationException e) {
            System.out.println("Set は変更できません!");
        }

        Map<String, Integer> modifiableMap = new HashMap<>();
        modifiableMap.put("Alice", 30);
        modifiableMap.put("Bob", 25);

        Map<String, Integer> immutableMap = Map.copyOf(modifiableMap);

        try {
            immutableMap.put("Charlie", 28);
        } catch (UnsupportedOperationException e) {
            System.out.println("Map は変更できません!");
        }
    }
}

2. copyOf メソッドの特徴と制約

null 要素は許可されない

ファクトリメソッド List.of、Set.of、Map.of と同様に、copyOf も要素・キー・値としての null を禁止します。元のコレクション/マップに null が含まれている場合、コピー中に NullPointerException が投げられます。

List<String> listWithNull = Arrays.asList("A", null, "B");
List<String> immutable = List.copyOf(listWithNull); // NullPointerException を送出する!

コレクションは本当に不変です

add/remove/put/replace の試みは UnsupportedOperationException になります。

元とコピーは独立しています

元のコレクションの変更はコピーに影響せず、その逆も同様です。

渡したコレクションがすでに不変なら — 同じオブジェクトが返されます

すでに不変のコレクション(たとえば List.of(...) の結果)を渡した場合、copyOf は同じオブジェクトを返します(メモリ節約)。

List<String> immutable = List.of("X", "Y");
List<String> copy = List.copyOf(immutable);

System.out.println(immutable == copy); // true

具体的な実装は保証されません

返されるコレクションの型は単に List、Set、Map です。必ずしも ArrayList、HashSet、HashMap とは限りません。実装の詳細(たとえば内部型や最適化)に依存しないでください。契約は不変性と対応するインタフェースであることだけです。

3. copyOf とラッパー (unmodifiable wrappers) の違い

Collections.unmodifiableList(list) は既存のコレクションに対するラッパーを作成します。元のコレクションが変更されると、その変更はラッパーにも見えるようになります。

List.copyOf(list) はオリジナルと無関係な新しい不変コレクションを作成します。

違いのデモ

import java.util.*;

public class WrapperVsCopyOf {
    public static void main(String[] args) {
        List<String> original = new ArrayList<>(List.of("A", "B"));

        // ラッパー
        List<String> wrapper = Collections.unmodifiableList(original);

        // コピー
        List<String> copy = List.copyOf(original);

        // 元を変更する
        original.add("C");

        System.out.println("ラッパー: " + wrapper); // [A, B, C]
        System.out.println("コピー: " + copy);      // [A, B]
    }
}

出力:

ラッパー: [A, B, C]
コピー: [A, B]

ラッパーは元の変更を反映し、コピーは不変のままです。

4. copyOf の実用的な利用シナリオ

メソッドから返す際のデータ保護

public class CurrencyService {
    private final List<String> currencies = new ArrayList<>(List.of("USD", "EUR", "JPY"));

    public List<String> getSupportedCurrencies() {
        // 返された結果を誰も変更できない
        return List.copyOf(currencies);
    }
}

これで呼び出し側のコードは、返されたリストから通貨を追加・削除できません。

アプリケーション層間の受け渡し

データが層間(DAO → サービス → コントローラ)を渡るときは、途中で誰もコレクションを変更しないことを保証するために copyOf を使いましょう。

マルチスレッドプログラムでの安全な公開

不変コレクションは、追加の同期なしにスレッド間でデータを安全に共有できる便利な方法です。

5. copyOf と不変コレクションのよくある誤り

誤り1: null を挿入しようとする。 copyOf(および of)は null を許しません。リスト/セットの要素としても、マップのキー/値としても不可です。コピー時に NullPointerException になります。

誤り2: ラッパーとコピーの取り違え。 copyOf は独立したコピーを作ります。元の変更はコピーに見えません。一方、Collections.unmodifiableList(list) は単に元のコレクションをラップするだけなので、元の変更はラッパーにそのまま反映されます。

誤り3: 不変コレクションを変更しようとする。 copyOf/of で得たコレクションに対する add/remove/put などの呼び出しは、UnsupportedOperationException を引き起こします。

誤り4: マップのファクトリの制限を忘れる。 たとえば Map.of() はキーと値のペアを最大 10 個までしかサポートしません。より多い場合は、Map.ofEntries(...) を使うか、可変のマップを組み立ててから Map.copyOf を適用します。

誤り5: 具体的な実装に依存する。 copyOf が返すのが ArrayList や HashSet であると期待しないでください。保証されるのはインタフェース(List/Set/Map)と不変性だけです。

1
タスク
JAVA 25 SELF, レベル 34, レッスン 1
ロック未解除
店舗のカート:ライブビューとスナップショット 🛒
店舗のカート:ライブビューとスナップショット 🛒
1
タスク
JAVA 25 SELF, レベル 34, レッスン 1
ロック未解除
ゲーム設定:リリース用の設定を固定する 🎮
ゲーム設定:リリース用の設定を固定する 🎮
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION