💔

JavaのCollections.unmodifiableListを勘違いしていた話

2024/12/14に公開

概要

Collections.unmodifiableListはリストに対する変更不能なビューを返すのであって、変更が不能なリストを返すわけではありません。
そのため、ビューの元の参照に変更を加えると、Collections.unmodifiableListで返ってきたリストも変更されます。

対象読者

JavaのCollections.unmodifiableListの理解を深めたい人。
Collections.unmodifiableListを使ったはずなのに何故かリストが書き換わる怪奇現象に出会った人。

unmodifiableListで得られたリストを書き換える

Collections.unmodifiableListで得られたリストは参照元と連動しています。
そのため、参照元に変更を加えるとCollections.unmodifiableListで得られたリストにも変更が伝達します。

List<Integer> mutlist = new ArrayList<Integer>();
List<Integer> immutlist = Collections.unmodifiableList(mutlist);

System.out.println(mutlist);
// -> []

System.out.println(immutlist);
// -> []

mutlist.add(0);

System.out.println(mutlist);
// -> [0]

System.out.println(immutlist);
// -> [0]
// ここが[]であることを期待してた。

仕様と勘違いしてたこと

この仕様は、Collections.unmodifiableListが、リストに対する変更不能なアクセス権限を与えることが目的であるという点では適当な動作です。しかし、私は新しく不変のリストが返ってくると勘違いをしていました。

外部から不変なリストを作る

それでは、変更が不能なリストはどのようにしたら実現できるでしょうか。
考えられる手段は二つあります。
一つ目はビューの元をメソッド内に隠蔽することです。これは、変更可能な元が外側にないのだから結果的に変更が不能と見做せる例です。
二つ目は適当な形で変更不能なリストを直接実装することで、外部から完全に不変なリストを作ることです。

今回はビューの元をメソッド内に隠蔽する方式で実装を行います。

public class MyCollections {
    :
    :
    public static <E> List<E> unmodifiableList(List<? extends E> list) {
        return Collections.unmodifiableList(new ArrayList<E>(list));
    }
    :
    :

新しくnew ArrayList<E>(list)でリストを作成することでビューの元をメソッド内に隠しています。

実際に動作を確認してみても、つながりが切れていることを確認できます。

List<Integer> mutlist = new ArrayList<Integer>();
List<Integer> immutlist = Unmodifiable.unmodifiableList(mutlist);

System.out.println(mutlist);
// -> []

System.out.println(immutlist);
// -> []

mutlist.add(0);

System.out.println(mutlist);
// -> [0]

System.out.println(immutlist);
// -> []

Discussion