Collections 的 unmodifiableXXX() 方法


如果你原本有個 CollectionMap 已收集了一些元素,現在打算傳遞這個物件,而且不希望拿到此物件的任何一方對它做出修改(Modify)操作,那麼可以使用 Collections 上提供的 unmodifiableXXX() 方法,那些方法會傳回一個不可修改的物件,如果單純只是取得元素是沒問題,如果呼叫了有副作用的 add()remove() 等方法,則會丟出 UnsupportedOperationException,例如:

jshell> List<String> names = new ArrayList<>();
names ==> []

jshell> names.add("Monica");
$2 ==> true

jshell> names.add("Justin");
$3 ==> true

jshell> List<String> unmodifiableNames = Collections.unmodifiableList(names);
unmodifiableNames ==> [Monica, Justin]

jshell> unmodifiableNames.get(0);
$5 ==> "Monica"

jshell> unmodifiableNames.add("Irene")
|  java.lang.UnsupportedOperationException thrown:
|        at Collections$UnmodifiableCollection.add (Collections.java:1056)
|        at (#6:1)

那麼,透過 unmodifiableXXX() 方法傳回的物件是不可變嗎?不是,傳回的物件只是無法修改(Unmodifiable),也就是僅僅不支援修改操作罷了,這是什麼意思?以上面的程式片段來說,如果我直接呼叫 names.add("Irene")unmodifiableNames 的內容也就跟著變動了:

jshell> names.add("Irene");
$7 ==> true

jshell> unmodifiableNames;
unmodifiableNames ==> [Monica, Justin, Irene]

之所以會如此,是因為 get()containsAll() 這類方法,只是單純委託給 unmodifiableXXX() 接收之物件(而 add() 等方法直接撰寫 throw new UnsupportedOperationException()),例如 unmodifiableCollection() 方法的實作是這樣的:

public static <T> Collection<T> unmodifiableCollection(Collection<? extends T> c) {
    return new UnmodifiableCollection<>(c);
}

static class UnmodifiableCollection<E> implements Collection<E>, Serializable {
    private static final long serialVersionUID = 1820017752578914078L;

    final Collection<? extends E> c;

    UnmodifiableCollection(Collection<? extends E> c) {
        if (c==null)
            throw new NullPointerException();
        this.c = c;
    }
    略...
    public boolean add(E e) {
        throw new UnsupportedOperationException();
    }
    public boolean remove(Object o) {
        throw new UnsupportedOperationException();
    }

    public boolean containsAll(Collection<?> coll) {
        return c.containsAll(coll);
    }    
    略...
}

不可變從來也沒在 Collections 上那些 unmodifiableXXX() 方法的保證中,畢竟名稱上也指出了,傳回的物件是不可修改,而不是不可變動。無論這是不是在玩文字遊戲,如果你要的是更進一步的不可變特性,那麼使用 CollectionsunmodifiableXXX() 傳回的物件,顯然是有所不足。