CodeGym /コース /JAVA 25 SELF /Mutable と Immutable のコレクション: 違いと使い分け

Mutable と Immutable のコレクション: 違いと使い分け

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

1. はじめに

平たく言えば、mutable コレクションは作成後に変更できるものです。要素の追加・削除・更新が可能です。Immutable(不変)コレクションは、作成後に変更できないコレクションです。ちょうど固まったコンクリートのようなもの——見ることも触ることもできますが、新しい形に作り直すことはできません。

mutable コレクションは鉛筆付きのノートのようなものです。書いたり、消したり、新しいメモを追加したりできます。immutable コレクションはラミネートしたページのようなものです。もはや誰も何も追加・削除できません。

可変(mutable)コレクションの例

Java では、標準コレクションのほとんどがデフォルトで可変です。例えば次のクラスがあります:

  • ArrayList
  • LinkedList
  • HashSet
  • TreeSet
  • HashMap
  • LinkedHashMap
  • など多数

例: ArrayList

import java.util.*;

List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.set(1, "Charlie"); // Bob を Charlie に置き換えた
names.remove("Alice");   // Alice を削除した
System.out.println(names); // [Charlie]

ここではコレクションに対して何でもできます。要素の追加・削除・交換。たとえば、ファイル読み込みやユーザー入力の処理中に動的にコレクションを構築するときに便利です。

2. 不変(immutable)コレクションの例

これらは Java 9 で導入され、すでに触れたことがあるかもしれませんが、使いこなすには慣れが必要です:

  • List.of(...)
  • Set.of(...)
  • Map.of(...)
  • List.copyOf(collection)
  • Set.copyOf(collection)
  • Map.copyOf(map)

例: List.of

List<String> planets = List.of("Mercury", "Venus", "Earth", "Mars");
System.out.println(planets); // [Mercury, Venus, Earth, Mars]
planets.add("Jupiter"); // UnsupportedOperationException をスローする!

コレクションを変更しようとすると実行時例外が発生します。

例: Collections.unmodifiableList

List<String> modifiable = new ArrayList<>(List.of("a", "b"));
List<String> unmodifiable = Collections.unmodifiableList(modifiable);
unmodifiable.add("c"); // UnsupportedOperationException!

ただし注意点があります。元のコレクションを変更すると、ラッパーも変化します。

modifiable.add("c");
System.out.println(unmodifiable); // [a, b, c] — 要素が現れた!

3. mutable と immutable の主な違い

特性 Mutable(可変) Immutable(不変)
要素を追加できるか? はい いいえ
要素を削除できるか? はい いいえ
要素を変更できるか? はい(例: set いいえ
スレッドセーフティ いいえ(デフォルト) はい(状態がないため変更対象がない)
null を追加できるか? はい(通常) いいえ(Java 9+ のファクトリーメソッドでは不可)
実装 ArrayListHashSet など List.ofSet.ofMap.ofcopyOf

4. なぜ不変コレクションが必要なのか

可変コレクションが柔軟なのに、なぜ不変が必要なのか——こう思うかもしれません。理由は多く、どれもコードの安全性・可読性・予測可能性に関わります。

安全性とエラー防止

コレクションを外部(メソッドやクラスの外)に渡すとき、うっかり中身を変更されないようにしたいものです。特に、初期化後に変更すべきでない「重要な」データを含む場合はなおさらです。

例:

public class Team {
    private final List<String> players;

    public Team(List<String> players) {
        // 不変のコピーを作成して、誰にもメンバーを差し替えられないようにする
        this.players = List.copyOf(players);
    }

    public List<String> getPlayers() {
        return players;
    }
}

これで、プレイヤー一覧を受け取った側のコードは、自分の「友だち」を勝手に追加することができません。

スレッドセーフティ

可変コレクションは、複数スレッドから同時にアクセスすると安全ではありません。逆に不変コレクションは、自由にスレッド間で共有できます——誰も改変できないからです。

デバッグの容易さ

コレクションが変わらないなら、中身を常に把握できます。どこか別の場所で「こっそり」変更されていないか心配する必要がありません。

他のコレクションのキーや値としての利用

不変オブジェクトは、Map のキーや Set の要素として理想的です。オブジェクトが追加後に変更される可能性があると、アクセスできなくなる危険があります(hashCodeequals を参照)。

5. 可変コレクションを使うべきとき

可変コレクションが向いているのは次のような場合です:

  • コレクションを段階的に、ループや複数のソースから構築する。
  • 追加・削除・ソートなど、頻繁な変更が必要。
  • コレクションが内部専用で、外部から壊される心配がない。

例: リストの構築

List<String> shoppingList = new ArrayList<>();
shoppingList.add("牛乳");
shoppingList.add("パン");
shoppingList.add("りんご");
// 構築が終わったら、不変版にできる
List<String> finalList = List.copyOf(shoppingList);

6. 不変コレクションを使うべきとき

  • 定数データの保持(例: 曜日の一覧)。
  • アプリケーション層間の受け渡し(例: DAO からサービスへ)。
  • メソッドからコレクションを返すときに、変更から保護する。
  • スレッドセーフティが重要なマルチスレッドの場面。

例: 定数データ

public static final List<String> WEEKDAYS = List.of(
    "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"
);

例: 外部へ渡す

public List<String> getReadOnlyNames() {
    return List.copyOf(names); // だれもリストを変更できない
}

7. 特徴と落とし穴

不変 ≠ スレッドセーフ

不変コレクションは変更からは守られますが、マルチスレッド環境での他の問題(たとえば、要素が可変オブジェクトである場合)まで自動的に防げるわけではありません。

List<List<String>> listOfLists = List.of(new ArrayList<>());
listOfLists.get(0).add("Oops!"); // 内部のリストは変更できてしまう!

ラッパー vs コピー

先ほど述べたように、Collections.unmodifiableList はラッパーであり、元のコレクションを変更するとラッパーも変わります。一方、List.copyOf は本当の独立したコピーを作成します。

List<String> base = new ArrayList<>(List.of("a", "b"));
List<String> wrap = Collections.unmodifiableList(base);
List<String> copy = List.copyOf(base);

base.add("c");
System.out.println(wrap); // [a, b, c] — 変化した!
System.out.println(copy); // [a, b] — そのまま!

NullPointerException

ファクトリーメソッド(List.ofSet.ofMap.of)では、null の追加はできません:

List<String> bad = List.of("a", null); // 作成時にすでに NullPointerException をスローする!

8. アプローチの比較: 長所と短所

可変コレクション

可変コレクションの最大の利点は柔軟性です。実行中に要素を追加・削除したり、プログラムのロジックに応じてコレクションを組み替えたりできます。一時的な構造を素早く作る、動的に変更する、といった用途に特に向いています。

しかし、その便利さには代償があります。可変コレクションは常に偶発的な介入のリスクを伴います。誰かがうっかりデータを変更してしまい、追跡しづらいバグにつながるかもしれません。マルチスレッドのプログラムではさらに深刻で、並行する変更が競合や予想外の不具合を招きやすくなります。データのライフサイクル管理も難しく、誰がいつコレクションを変更し得るかを常に意識する必要があります。

不変コレクション

不変コレクションは安心して扱えます。背後で勝手に変更されないことが保証され、コードはより安全で、デバッグやテストも容易になります。追加のロックなしでスレッド間で受け渡しやすいのも利点です。

一方で、性能を犠牲にする場面もあります。新しい要素を追加するたびにコレクションの新しいコピーを作る必要があり、その分のメモリと時間がかかります。また、大きく複雑な構造を最初から不変の形で組み立てるのは不便なことも多く、通常は一時的に可変コレクションで構築し、準備が整ったら「凍結」します。

9. よくある間違い

ミス 1: 可変コレクションを外部に返す。 メソッドから通常の ArrayList を返すと、外部のコードが要素を自由に追加・削除できてしまいます。これは追跡が非常に難しいバグにつながり得ます。

ミス 2: コピーではなくラッパーを使ってしまう。 Collections.unmodifiableList を使っても、元のコレクションがどこかで変更されるなら、その「不変」は幻想にすぎません。

ミス 3: immutable コレクションの内部に可変オブジェクトがある。 コレクション自体が不変でも、要素が可変なら状態は変わり得ます。思わぬ変更につながります。

ミス 4: ファクトリーメソッドで作成したコレクションに null を追加しようとする。 従来のコレクションと異なり、新しいファクトリーメソッドは null を許しません。NullPointerException になります。

ミス 5: 具体的な実装を期待してしまう。 List.ofSet.of で作られるコレクションは、実装タイプが保証されません(必ずしも ArrayListHashSet とは限りません)。これに依存しないでください。

1
タスク
JAVA 25 SELF, レベル 34, レッスン 3
ロック未解除
動物の在庫: ライブビューか静的スナップショットか? 🐾
動物の在庫: ライブビューか静的スナップショットか? 🐾
1
タスク
JAVA 25 SELF, レベル 34, レッスン 3
ロック未解除
プレイリスト: コレクション自体は不変ですが、中の曲は変更されます 🎶
プレイリスト: コレクション自体は不変ですが、中の曲は変更されます 🎶
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION