Immutable(不可變)集合

  不可變集合,顧名思義就是說集合是不可被修改的。集合的數據項是在創建的時候提供,並且在整個生命週期中都不可改變。

  爲什麼要用immutable對象?immutable對象有以下的優點:
    1.對不可靠的客戶代碼庫來說,它使用安全,可以在未受信任的類庫中安全的使用這些對象
    2.線程安全的:immutable對象在多線程下安全,沒有競態條件
    3.不需要支持可變性, 可以儘量節省空間和時間的開銷. 所有的不可變集合實現都比可變集合更加有效的利用內存 (analysis)
    4.可以被使用爲一個常量,並且期望在未來也是保持不變的

  immutable對象可以很自然地用作常量,因爲它們天生就是不可變的對於immutable對象的運用來說,它是一個很好的防禦編程(defensive programming)的技術實踐。

  JDK中實現immutable集合

  在JDK中提供了Collections.unmodifiableXXX系列方法來實現不可變集合, 但是存在一些問題,下面我們先看一個具體實例:

複製代碼
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Test;

public class ImmutableTest {
    @Test
    public void testJDKImmutable(){                                                                                                                                                                                                                                    
        List<String> list=new ArrayList<String>();                                                                               
        list.add("a");                                                                                                           
        list.add("b");                                                                                                           
        list.add("c");
        
        System.out.println(list);
        
        List<String> unmodifiableList=Collections.unmodifiableList(list); 
        
        System.out.println(unmodifiableList);
        
        List<String> unmodifiableList1=Collections.unmodifiableList(Arrays.asList("a","b","c")); 
        System.out.println(unmodifiableList1);
        
        String temp=unmodifiableList.get(1);
        System.out.println("unmodifiableList [0]:"+temp);
                
        list.add("baby");
        System.out.println("list add a item after list:"+list);
        System.out.println("list add a item after unmodifiableList:"+unmodifiableList);
        
        unmodifiableList1.add("bb");
        System.out.println("unmodifiableList add a item after list:"+unmodifiableList1);
        
        unmodifiableList.add("cc");
        System.out.println("unmodifiableList add a item after list:"+unmodifiableList);        
    }
}
複製代碼

  輸出:

複製代碼
[a, b, c]
[a, b, c]
[a, b, c]
unmodifiableList [0]:b
list add a item after list:[a, b, c, baby]
list add a item after unmodifiableList1:[a, b, c, baby]
複製代碼

  說明:Collections.unmodifiableList實現的不是真正的不可變集合,當原始集合修改後,不可變集合也發生變化。不可變集合不可以修改集合數據,當強制修改時會報錯,實例中的最後兩個add會直接拋出不可修改的錯誤。

  總結一下JDK的Collections.unmodifiableXXX方法實現不可變集合的一些問題:

  1.它用起來笨拙繁瑣你不得不在每個防禦性編程拷貝的地方用這個方法
  2.它不安全:如果有對象reference原始的被封裝的集合類,這些方法返回的集合也就不是正真的不可改變。
  3.效率低:因爲它返回的數據結構本質仍舊是原來的集合類,所以它的操作開銷,包括併發下修改檢查,hash table裏的額外數據空間都和原來的集合是一樣的。

  Guava的immutable集合

  Guava提供了對JDK裏標準集合類裏的immutable版本的簡單方便的實現,以及Guava自己的一些專門集合類的immutable實現。當你不希望修改一個集合類,或者想做一個常量集合類的時候,使用immutable集合類就是一個最佳的編程實踐。

注意:每個Guava immutable集合類的實現都拒絕null值。我們做過對Google內部代碼的全面的調查,並且發現只有5%的情況下集合類允許null值,而95%的情況下都拒絕null值。萬一你真的需要能接受null值的集合類,你可以考慮用Collections.unmodifiableXXX。

  Immutable集合使用方法:
  一個immutable集合可以有以下幾種方式來創建:
  1.用copyOf方法, 譬如, ImmutableSet.copyOf(set)
  2.使用of方法,譬如,ImmutableSet.of("a", "b", "c")或者ImmutableMap.of("a", 1, "b", 2)
  3.使用Builder類

  實例:

複製代碼
@Test
    public void testGuavaImmutable(){
        
        List<String> list=new ArrayList<String>();
        list.add("a");
        list.add("b");
        list.add("c");
        System.out.println("list:"+list);
        
        ImmutableList<String> imlist=ImmutableList.copyOf(list);
        System.out.println("imlist:"+imlist);
        
        ImmutableList<String> imOflist=ImmutableList.of("peida","jerry","harry");
        System.out.println("imOflist:"+imOflist);
        
        ImmutableSortedSet<String> imSortList=ImmutableSortedSet.of("a", "b", "c", "a", "d", "b");
        System.out.println("imSortList:"+imSortList);
        
         list.add("baby");
         System.out.println("list add a item after list:"+list);
         System.out.println("list add a item after imlist:"+imlist);
             
         ImmutableSet<Color> imColorSet =
               ImmutableSet.<Color>builder()
                   .add(new Color(0, 255, 255))
                   .add(new Color(0, 191, 255))
                   .build();
         
         System.out.println("imColorSet:"+imColorSet);       
    }
複製代碼

  輸出:

複製代碼
list:[a, b, c]
imlist:[a, b, c]
imOflist:[peida, jerry, harry]
imSortList:[a, b, c, d]
list add a item after list:[a, b, c, baby]
list add a item after imlist:[a, b, c]
imColorSet:[java.awt.Color[r=0,g=255,b=255], java.awt.Color[r=0,g=191,b=255]]
複製代碼

  對於排序的集合來說有例外,因爲元素的順序在構建集合的時候就被固定下來了。譬如,ImmutableSet.of("a", "b", "c", "a", "d", "b"),對於這個集合的遍歷順序來說就是"a", "b", "c", "d"。

  更智能的copyOf

  copyOf方法比你想象的要智能,ImmutableXXX.copyOf會在合適的情況下避免拷貝元素的操作-先忽略具體的細節,但是它的實現一般都是很“智能”的。譬如:

複製代碼
@Test
    public void testCotyOf(){
        ImmutableSet<String> imSet=ImmutableSet.of("peida","jerry","harry","lisa");
        System.out.println("imSet:"+imSet);
        ImmutableList<String> imlist=ImmutableList.copyOf(imSet);
        System.out.println("imlist:"+imlist);
        ImmutableSortedSet<String> imSortSet=ImmutableSortedSet.copyOf(imSet);
        System.out.println("imSortSet:"+imSortSet);
        
        List<String> list=new ArrayList<String>();
        for(int i=0;i<20;i++){
            list.add(i+"x");
        }
        System.out.println("list:"+list);
        ImmutableList<String> imInfolist=ImmutableList.copyOf(list.subList(2, 18));
        System.out.println("imInfolist:"+imInfolist);
        int imInfolistSize=imInfolist.size();
        System.out.println("imInfolistSize:"+imInfolistSize);
        ImmutableSet<String> imInfoSet=ImmutableSet.copyOf(imInfolist.subList(2, imInfolistSize-3));
        System.out.println("imInfoSet:"+imInfoSet);
    }
複製代碼

  輸出: 

複製代碼
imSet:[peida, jerry, harry, lisa]
imlist:[peida, jerry, harry, lisa]
imSortSet:[harry, jerry, lisa, peida]
list:[0x, 1x, 2x, 3x, 4x, 5x, 6x, 7x, 8x, 9x, 10x, 11x, 12x, 13x, 14x, 15x, 16x, 17x, 18x, 19x]
imInfolist:[2x, 3x, 4x, 5x, 6x, 7x, 8x, 9x, 10x, 11x, 12x, 13x, 14x, 15x, 16x, 17x]
imInfolistSize:16
imInfoSet:[4x, 5x, 6x, 7x, 8x, 9x, 10x, 11x, 12x, 13x, 14x]
複製代碼

  在這段代碼中,ImmutableList.copyOf(imSet)會智能地返回時間複雜度爲常數的ImmutableSet的imSet.asList()。
  一般來說,ImmutableXXX.copyOf(ImmutableCollection)會避免線性複雜度的拷貝操作。如在以下情況:
  這個操作有可能就利用了被封裝數據結構的常數複雜度的操作。但例如ImmutableSet.copyOf(list)不能在常數複雜度下實現。
  這樣不會導致內存泄漏-例如,你有個ImmutableList<String> imInfolist,然後你顯式操作ImmutableList.copyOf(imInfolist.subList(0, 10))。這樣的操作可以避免意外持有不再需要的在hugeList裏元素的reference。
  它不會改變集合的語意-像ImmutableSet.copyOf(myImmutableSortedSet)這樣的顯式拷貝操作,因爲在ImmutableSet裏的hashCode()和equals()的含義和基於comparator的ImmutableSortedSet是不同的。
  這些特性有助於最優化防禦性編程的性能開銷。

  asList方法

  所有的immutable集合都以asList()的形式提供了ImmutableList視圖(view)。譬如,你把數據放在ImmutableSortedSet,你就可以調用sortedSet.asList().get(k)來取得前k個元素的集合。
  返回的ImmutableList常常是個常數複雜度的視圖,而不是一個真的拷貝。也就是說,這個返回集合比一般的List更智能-譬如,它會更高效地實現contains這樣的方法。

  實例:

複製代碼
@Test
    public void testAsList(){
        ImmutableList<String> imList=ImmutableList.of("peida","jerry","harry","lisa","jerry");
        System.out.println("imList:"+imList);
        ImmutableSortedSet<String> imSortList=ImmutableSortedSet.copyOf(imList);
        System.out.println("imSortList:"+imSortList);
        System.out.println("imSortList as list:"+imSortList.asList());
    }
複製代碼

  輸出:

imList:[peida, jerry, harry, lisa, jerry]
imSortList:[harry, jerry, lisa, peida]
imSortList as list:[harry, jerry, lisa, peida]

  Guava集合和不可變對應關係

可變集合類型可變集合源:JDK or Guava?Guava不可變集合
CollectionJDKImmutableCollection
ListJDKImmutableList
SetJDKImmutableSet
SortedSet/NavigableSetJDKImmutableSortedSet
MapJDKImmutableMap
SortedMapJDKImmutableSortedMap
MultisetGuavaImmutableMultiset
SortedMultisetGuavaImmutableSortedMultiset
MultimapGuavaImmutableMultimap
ListMultimapGuavaImmutableListMultimap
SetMultimapGuavaImmutableSetMultimap
BiMapGuavaImmutableBiMap
ClassToInstanceMapGuavaImmutableClassToInstanceMap
TableGuavaImmutableTable

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章