不変List生成は「絶対に変わらない約束」をコードに刻む技術
不変List(Immutable List)は、「一度作ったら中身を二度と変えられないList」です。add も remove も set もできません。
「え、そんな不便なもの要る?」と思うかもしれませんが、
業務システムではむしろ「勝手に変わらない」ことが大きな安心材料になります。
マスタの一覧、定数的な選択肢、権限のリストなど、
「変わったら困るもの」を不変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));
}
}
JavaJava 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(...) に置き換えてみてください。
その小さな変更が、
「コレクションの“変わる/変わらない”を意識して設計できるエンジニア」への、
確かな一歩になります。
