Java Tips | コレクション:不変List生成

Java Java
スポンサーリンク

不変List生成は「絶対に変わらない約束」をコードに刻む技術

不変List(Immutable List)は、「一度作ったら中身を二度と変えられないList」です。
addremoveset もできません。

「え、そんな不便なもの要る?」と思うかもしれませんが、
業務システムではむしろ「勝手に変わらない」ことが大きな安心材料になります。

マスタの一覧、定数的な選択肢、権限のリストなど、
「変わったら困るもの」を不変Listにしておくと、
バグの入り込む余地がぐっと減ります。


Java標準での不変Listの作り方を整理する

Java 9 以降:List.of(...) で一発生成

Java 9 以降なら、不変Listは List.of(...) で簡単に作れます。

import java.util.List;

public class ImmutableListSample {

    public static void main(String[] args) {
        List<String> statuses = List.of("NEW", "IN_PROGRESS", "DONE");

        System.out.println(statuses); // [NEW, IN_PROGRESS, DONE]

        // どれも例外(UnsupportedOperationException)
        statuses.add("CANCEL");
        statuses.remove("NEW");
        statuses.set(0, "XXX");
    }
}
Java

ここでの重要ポイントは二つです。

一つ目は、「見た目は普通の List だが、変更操作はすべて例外になる」ということです。
つまり、「読み取り専用」として安心して渡せます。

二つ目は、「List.of で作ったListを、フィールドや定数として公開するときは、
“変えちゃダメなもの”だと一目で分かる」ということです。

例えば、こんな感じです。

public final class Statuses {

    private Statuses() {}

    public static final List<String> VALID_STATUSES =
            List.of("NEW", "IN_PROGRESS", "DONE");
}
Java

このクラスを見た人は、「VALID_STATUSES は固定の一覧なんだな」とすぐに理解できます。

Java 8 以前:Collections.unmodifiableList でラップする

Java 8 以前には List.of がないので、
Collections.unmodifiableList を使って不変Listを作ります。

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class ImmutableListLegacy {

    public static final List<String> VALID_STATUSES =
            Collections.unmodifiableList(
                    Arrays.asList("NEW", "IN_PROGRESS", "DONE")
            );

    public static void main(String[] args) {
        System.out.println(VALID_STATUSES); // [NEW, IN_PROGRESS, DONE]
        VALID_STATUSES.add("CANCEL");       // UnsupportedOperationException
    }
}
Java

ここで深掘りしたいポイントは、「ラップ元のListを外に出さない」ことです。

unmodifiableList は「ラップしているListへの変更を禁止するビュー」を返しているだけなので、
元の Arrays.asList(...) の参照をどこかに持っていてそこから set されると、中身は変わってしまいます。

だからこそ、

ラップ元のListはその場で作って、そのまま unmodifiableList に渡す
ラップ元のListへの参照を他に持たない

という書き方が重要になります。


不変Listを返すユーティリティを用意する

「呼び出し側に“変えられない”ことを伝える」ための窓口

業務メソッドの戻り値として、「不変Listを返したい」こともよくあります。
そのときに毎回 Collections.unmodifiableList と書くのは少し重いので、
ユーティリティにまとめてしまうとスッキリします。

import java.util.Collections;
import java.util.List;

public final class ImmutableLists {

    private ImmutableLists() {}

    public static <T> List<T> copyOf(List<T> source) {
        if (source == null || source.isEmpty()) {
            return Collections.emptyList();
        }
        return Collections.unmodifiableList(List.copyOf(source));
    }
}
Java

Java 9 以降なら、もっとシンプルに List.copyOf をそのまま使っても構いません。

public static <T> List<T> copyOf(List<T> source) {
    if (source == null || source.isEmpty()) {
        return List.of();
    }
    return List.copyOf(source); // これ自体が不変Listを返す
}
Java

使い方はこうです。

List<String> mutable = new java.util.ArrayList<>();
mutable.add("A");
mutable.add("B");

List<String> immutable = ImmutableLists.copyOf(mutable);

// immutable.add("C"); // UnsupportedOperationException
Java

ここでの重要ポイントは、「元のListとは独立した“不変のコピー”を返している」ことです。
呼び出し側が元のListをあとで変更しても、
不変List側は影響を受けません。


「不変Listにしておくと安心な場所」を具体的にイメージする

例1:マスタデータや定数的な選択肢

例えば、画面のプルダウンに出す「固定の選択肢」は、不変Listにしておくと安心です。

public final class Genders {

    private Genders() {}

    public static final List<String> OPTIONS =
            List.of("MALE", "FEMALE", "OTHER");
}
Java

どこかのコードが OPTIONS.add("UNKNOWN") しようとすると例外になり、
「勝手に選択肢が増えていた」という事故を防げます。

例2:設定値を読み込んだ後の「確定値」

設定ファイルやDBから一度だけ読み込んで、
あとは読み取り専用で使うようなリストも、不変Listに向いています。

public final class ConfigHolder {

    private final List<String> allowedIps;

    public ConfigHolder(List<String> allowedIps) {
        this.allowedIps = List.copyOf(allowedIps); // 不変コピー
    }

    public List<String> getAllowedIps() {
        return allowedIps;
    }
}
Java

ここでの重要ポイントは、「コンストラクタで不変コピーを作ってしまう」ことです。
これにより、外から渡されたListがあとで変更されても、
ConfigHolder の内部状態は変わりません。


「不変List」と「読み取り専用にしたいだけのList」を区別する

呼び出し側に“意図”を伝えるための設計

ときどき、
「本当は中身を変えたくないけど、実装の都合で可変Listを返している」
というコードがあります。

// あまり良くない例
public List<String> getItems() {
    return items; // 内部の可変Listをそのまま返している
}
Java

これだと、呼び出し側が getItems().add(...) できてしまい、
内部状態がどこからでも書き換えられる危険な設計になります。

「外からは変えてほしくない」と思っているなら、
不変Listを返すべきです。

public List<String> getItems() {
    return List.copyOf(items); // 不変コピーを返す
}
Java

ここでの重要ポイントは、「不変Listは“意図を伝える道具”でもある」ということです。
「このリストは変えちゃダメ」というメッセージを、
型と実装で表現しているわけです。


まとめ:不変List生成で身につけてほしい感覚

不変List生成は、単に「変更できないListを作るテクニック」ではなく、
「ここは絶対に変わってほしくない」という意図をコードに刻むための技術です。

変わらない一覧・マスタ・選択肢は List.of(...)List.copyOf(...) で不変にする。
Java 8 以前なら Collections.unmodifiableList でラップし、元Listは外に出さない。
メソッドの戻り値で「外から変更されたくない」ものは、不変Listを返す。
コンストラクタで不変コピーを作っておくと、外部からの変更の影響を受けない。

もしあなたのコードのどこかに、

public static final List<String> OPTIONS = new ArrayList<>();
Java

のような「定数なのに可変List」があったら、
それを一度 List.of(...)List.copyOf(...) に置き換えてみてください。

その小さな変更が、
「コレクションの“変わる/変わらない”を意識して設計できるエンジニア」への、
確かな一歩になります。

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