【Java学習|実務向け】Java 10以降の必須テクニック:List.copyOf() 等を用いた「不変コレクション」の安全な作成方法

1. 導入:なぜ「不変コレクション」が重要なのか

実務において、コレクションをメソッド間で受け渡す際、意図せず中身が書き換えられてバグを生むケースは少なくありません。特に、リストのコピーを作って渡したつもりでも、参照渡しによって元のリストまで変更されてしまうというミスは、デバッグが困難なバグの温床です。Java 10で導入された List.copyOf() 等の静的ファクトリメソッドは、既存コレクションの「不変(Immutable)」なコピーを安全かつ簡潔に作成する手段を提供し、こうした副作用を未然に防ぐための強力な武器となります。

2. 基礎知識:不変(Immutable)コレクションとは

不変コレクションとは、作成後に要素の追加・削除・置換が一切できないコレクションのことです。これらを変更しようとすると、UnsupportedOperationException がスローされます。
Java 9以前の Collections.unmodifiableList() は、「元のリストへの参照」をラップするものでした。つまり、元のリストを外部から書き換えると、不変とされているはずのリストの中身も変わってしまうという弱点がありました。一方、Java 10の copyOf() メソッドは、完全に独立した新しいコレクションを作成するため、より安全な「真の不変性」を保証します。

3. 実装/解決策:copyOf() の活用

copyOf() メソッドを使用する際のポイントは以下の通りです。
1. Nullの禁止:コピー元のコレクションにnullが含まれていると NullPointerException が発生します。
2. 要素の変更禁止:作成されたコレクション自体を変更しようとすると実行時エラーになります。
3. 防御的コピー:外部から渡された可変リストを、内部で不変に変換して保持する「防御的コピー」として最適です。

4. サンプルプログラム

以下のコードは、可変リストを不変リストに変換し、安全に扱う例です。

import java.util.ArrayList;
import java.util.List;

public class ImmutableExample {
    public static void main(String[] args) {
        // 1. 可変リストを作成
        List mutableList = new ArrayList<>();
        mutableList.add("Java");
        mutableList.add("Spring");

        // 2. copyOf() を使用して不変リストを作成
        // これ以降、immutableList は変更不可となります
        List immutableList = List.copyOf(mutableList);

        // 3. 元のリストを変更しても、不変リストには影響しない(安全!)
        mutableList.add("Hibernate");
        
        System.out.println("元のリスト: " + mutableList);    // [Java, Spring, Hibernate]
        System.out.println("不変リスト: " + immutableList); // [Java, Spring]

        // 4. 不変リストを操作しようとするとエラーになることを確認
        try {
            immutableList.add("New Item");
        } catch (UnsupportedOperationException e) {
            System.err.println("エラー: 不変リストは変更できません");
        }
    }
}

5. 応用・注意点

現場で活用する際の注意点は以下の3点です。

要素自体の不変性:copyOf() は「コレクションの構造」を不変にします。もしリストの中身が「ミュータブルなオブジェクト(自作のDTOなど)」の場合、そのオブジェクトのフィールドは変更可能です。リスト自体を不変にしても、中身のオブジェクトのプロパティまで保護されるわけではない点に注意してください。
Nullチェックの厳格化:前述の通り、copyOf() はnullを許容しません。もしnullが含まれる可能性があるリストを扱う場合は、事前に stream().filter(Objects::nonNull).toList() などでフィルタリングする処理を挟むのが一般的です。
パフォーマンス:コピーを作成するため、メモリ消費とCPU負荷がわずかに発生します。頻繁に更新されるコレクションには適しませんが、設定値の保持や、APIの戻り値として「状態を変えさせない」ことを保証したいケースでは、積極的に採用すべきです。

コメント

タイトルとURLをコピーしました