【Guava 教學】(6)不可變群集
【Guava 教學】(5)程式 90% 比率在管理與處理錯誤 << 前情 不可變(Immutability)是函數式程式設計(Functional programming)的基本特性之一。有不少說法是這麼描述:純函數式語言中的變數(Variable)是不可變(Immutable)。是這樣的嗎?基本上沒錯,不過嚴格來說,這樣的說法,是從計算機科學來解釋「變數」這個詞,就如同維基百科上 計算機科學上對變數 的條目說明是: 可視為在電腦記憶體裏可修改的、存在值的命名空間。 然而,在純函數式語言中對「變數」這個詞,要從數學上來解釋,就如同維基百科上 數學上對變數的修目說明是: 用於開放句子,表示尚未清楚的值(即未知數),或一個可代入的值。 在純函數式語言中,當你說 如果變數不可變,那設計出來的函數或物件方法就不會有副作用(Side effect),若物件的方法不會有副作用,那麼物件狀態也會是不可變,不可變物件(Immutable object)有許多好處,像是在並行(Concurrent)程式設計時,就不用擔心那些執行緒共用競爭的問題;在面對資料處理問題若需要一些群集(Collection)物件,像是有序的清單(List)、收集不重複物件的集合(Set)等,如果這些群集物件不可變,那麼就有可能共用資料結構,達到節省時間及空間之目的。 Java 在設計群集框架時,並沒有為不可變群集物件設計專用型態,看看 在這樣的聲明下,如果你原本有個群集已收集了一些物件,現在打算傳遞這個群集,而且不希望拿到這個群集的任何一方對它做出修改(Modify)操作,那麼可以使用 ... public static Collection unmodifiableCollection(Collection<? extends T> c) { return new UnmodifiableCollection<>(c); } static class UnmodifiableCollection implements Collection, 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 int size() {return c.size();} public boolean isEmpty() {return c.isEmpty();} public boolean contains(Object o) {return c.contains(o);} public Object[] toArray() {return c.toArray();} public T[] toArray(T[] a) {return c.toArray(a);} public String toString() {return c.toString();} public Iterator iterator() { return new Iterator() { private final Iterator<? extends E> i = c.iterator(); public boolean hasNext() {return i.hasNext();} public E next() {return i.next();} public void remove() { throw new UnsupportedOperationException(); } }; } 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); } public boolean addAll(Collection<? extends E> coll) { throw new UnsupportedOperationException(); } public boolean removeAll(Collection<?> coll) { throw new UnsupportedOperationException(); } public boolean retainAll(Collection<?> coll) { throw new UnsupportedOperationException(); } public void clear() { throw new UnsupportedOperationException(); } } ... 那麼,透過 Collections that do not support modification operations (such as add, remove and clear) are referred to as unmodifiable. 這是什麼意思?簡單來說,如果你有個 Collections that additionally guarantee that no change in the Collection object will be visible are referred to as immutable. 所以,不可變從來也沒在 Guava 對 JDK 的 List<String> nameList = ImmutableList.of("Duke", "Java", "Oracle"); Set<String> nameSet = ImmutableSet.of("Duke", "Java", "Oracle"); 上例傳回的實例分別會是 Map<String, Integer> userDB = ImmutableMap.of("Duke", 123, "Java", 456); 如果你需要收集資料,最後取得一個 ImmutableList.Builder<Integer> builder = ImmutableList.builder(); for(String arg : args) { builder.add(Integer.parseInt(arg)); } List<Integer> numbers = builder.build(); 如果你已經有陣列、 public static void doSome(Collection<String> names) { List<String> immutableNames = ImmutableList.copyOf(names); ... } 正如 舉例而言,如果將
那麼
... final class SingletonImmutableList<E> extends ImmutableList<E> { final transient E element; SingletonImmutableList(E element) { this.element = checkNotNull(element); } @Override public E get(int index) { Preconditions.checkElementIndex(index, 1); return element; } ... } 至於 ... @Override public boolean contains(Object target) { if (target == null) { return false; } for (int i = Hashing.smear(target.hashCode()); true; i++) { Object candidate = table[i & mask]; if (candidate == null) { return false; } if (candidate.equals(target)) { return true; } } } ... 這些原始碼的探討,其實在回應本文一開始談到的,群集物件若是不可變,那麼就有可能共用資料結構,達到節省時間及空間之目的。不可變特性並非僅存於函數式程式設計的世界,在命令式程式設計(Imperative programming)世界中,在某些場合若可善用不可變特性,就可避免因狀態可變而帶來的問題。 Guava 並不特別強調函數式的概念,不過實際上它提出不可變群集程式庫,並非只是單純地令群集不可變,在實作上也確實善用了不可變的益處,你可以查找更多 Guava 不可變群集的相關程式碼,藉此就可以看到更多有趣的設計與概念。 |