BambTech

コンピュータサイエンスの学習記録等です。

変更不可能なコレクション

今日とあるコードを読んでいて JavaunmodifiedMap なるものがでてきたから調べてみた備忘録

変更不可能なコレクション

変更不可能なコレクションとして2つの意味合いを持たせたワードがあるらしい
1つ目は unmodified で、2つ目が immutable
Oracle のドキュメント に使い分けが書いてあって、具体的には以下の文言が書いてある。

- Collections that do not support modification operations (such as add, remove and clear) are referred to as unmodifiable. Collections that are not unmodifiable are modifiable.
- Collections that additionally guarantee that no change in the Collection object will be visible are referred to as immutable. Collections that are not immutable are mutable.

正直これだけ読んでもいまいち意味がわからなかったから実際に手元で動かしてみた。

unmodified

結論から言うと、unmodified はあくまで unmodified とされていて参照をもっている変数からの修正はできないけど、変数持っている参照先のコレクションからは変更可能というものっぽい。うまく言えないけど。

    public void test() {
        List<String> modifiableList = new ArrayList<>();
        modifiableList.add("a");

        System.out.println("modifiableList: " + modifiableList);
        System.out.println("--");

        assertEquals(1, modifiableList.size());

        List<String> unModifiableList = Collections.unmodifiableList(modifiableList);
        modifiableList.add("b");

        boolean exceptionThrown = false;
        try {
            unModifiableList.add("b");
            fail("add supported for unModifiableList!!");
        } catch (UnsupportedOperationException e) {
            exceptionThrown = true;
            System.out.println("unModifiableList.add() not supported");
        }
        assertTrue(exceptionThrown);

        System.out.println("modifiableList: " + modifiableList);
        System.out.println("unModifiableList: " + unModifiableList);

        assertEquals(2, modifiableList.size());
        assertEquals(2, unModifiableList.size());
    }

// 出力結果
// modifiableList: [a]
// --
// unModifiableList.add() not supported
// modifiableList: [a, b]
// unModifiableList: [a, b]
// --

上記コードでは何をしているかと言うと、 modifiableList への参照を unModifiableList が持っていて、unModifiableList からは直接リストへの変更ができないようになっている。 実際のコードを抜粋すると

    static class UnmodifiableList<E> extends UnmodifiableCollection<E>
                                  implements List<E> {
        private static final long serialVersionUID = -283967356065247728L;

        final List<? extends E> list;

        UnmodifiableList(List<? extends E> list) {
            super(list);
            this.list = list;
        }

        public boolean equals(Object o) {return o == this || list.equals(o);}
        public int hashCode()           {return list.hashCode();}

        public E get(int index) {return list.get(index);}
        public E set(int index, E element) {
            throw new UnsupportedOperationException();
        }
        public void add(int index, E element) {
            throw new UnsupportedOperationException();
        }
        public E remove(int index) {
            throw new UnsupportedOperationException();
        }

set や add, remove といったメソッドに対しては無条件で例外を投げるようになっている。もはやこのメソッドを準備しておく意味はあるのだろうか、、

Immutable

僕は eclipse collection の immutable しかいじったことがないから、ここでは eclipse collection でやってみる。

    public void test() {
        List<String> mutableList = new ArrayList<>();
        mutableList.add("a");

        ImmutableList<String> immutableList = Lists.immutable.ofAll(mutableList);
        mutableList.add("b");

        assertNotEquals(mutableList.size(), immutableList.size());
        System.out.println("mutableList: " + mutableList);
        System.out.println("immutableList: " + immutableList);
    }

// 出力結果
// mutableList: [a, b]
// immutableList: [a]

今回は mutable の方に要素を追加しても immutable の方には何の変化もなかった。おそらく、immutable では参照を持っているのではなくもはや新しくコレクションを clone() しているように見えるけど、一応 ImmutableListFactoryImpl あたりを追ってみる。

    @Override
    public <T> ImmutableList<T> ofAll(Iterable<? extends T> items)
    {
        return this.withAll(items);
    }

    @Override
    public <T> ImmutableList<T> withAll(Iterable<? extends T> items)
    {
        if (items instanceof ImmutableList<?>)
        {
            return (ImmutableList<T>) items;
        }
        if (items instanceof List && items instanceof RandomAccess)
        {
            return this.withList((List<T>) items);
        }
        if (Iterate.isEmpty(items))
        {
            return this.empty();
        }
        return this.of((T[]) Iterate.toArray(items));
    }

    private <T> ImmutableList<T> withList(List<T> items)
    {
        switch (items.size())
        {
            case 0:
                return this.empty();
            case 1:
                return this.of(items.get(0));
            case 2:
                return this.of(items.get(0), items.get(1));
            case 3:
                return this.of(items.get(0), items.get(1), items.get(2));
            case 4:
                return this.of(items.get(0), items.get(1), items.get(2), items.get(3));
            case 5:
                return this.of(items.get(0), items.get(1), items.get(2), items.get(3), items.get(4));
            case 6:
                return this.of(items.get(0), items.get(1), items.get(2), items.get(3), items.get(4), items.get(5));
            case 7:
                return this.of(items.get(0), items.get(1), items.get(2), items.get(3), items.get(4), items.get(5), items.get(6));
            case 8:
                return this.of(items.get(0), items.get(1), items.get(2), items.get(3), items.get(4), items.get(5), items.get(6), items.get(7));
            case 9:
                return this.of(items.get(0), items.get(1), items.get(2), items.get(3), items.get(4), items.get(5), items.get(6), items.get(7), items.get(8));
            case 10:
                return this.of(items.get(0), items.get(1), items.get(2), items.get(3), items.get(4), items.get(5), items.get(6), items.get(7), items.get(8), items.get(9));

            default:
                return ImmutableArrayList.newListWith((T[]) items.toArray());
        }
    }

    public static <E> ImmutableArrayList<E> newListWith(E... elements)
    {
        return new ImmutableArrayList<>(elements.clone());
    }

ここら辺くらいまで見ればもうわかる感じ(最後のメソッドだけ ImmutableArrayList にある)。of メソッドではそれぞれの値に対して新しくコレクションを作っていそうで、default では完全にクローンしている。だから、mutableListimmutableList では最終的にアクセスしにいくメモリが全く異なっていて、かつ immutable には変更をするメソッドがそもそも存在していないから変更不可っぽい。

感想

コードまで追いかけると Oracle のドキュメント に書いてあることはしっかり理解できた。僕が知らない時代に unmodified も immutable もできているけど、とりあえず変更不可能っぽい unmodified を作ってみたはいいもののあまりにも使いにくすぎて immutable ができたのかな。まぁ物によってはちゃんと使い分けもできそうだけど。