不可變集合,顧名思義就是說集合是不可被修改的。集合的數據項是在建立的時候提供,而且在整個生命週期中都不可改變。java
爲何要用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裏的額外數據空間都和原來的集合是同樣的。ui
Guava的immutable集合spa
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:16imInfoSet:[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不可變集合 |
Collection | JDK | ImmutableCollection |
List | JDK | ImmutableList |
Set | JDK | ImmutableSet |
SortedSet/NavigableSet | JDK | ImmutableSortedSet |
Map | JDK | ImmutableMap |
SortedMap | JDK | ImmutableSortedMap |
Multiset | Guava | ImmutableMultiset |
SortedMultiset | Guava | ImmutableSortedMultiset |
Multimap | Guava | ImmutableMultimap |
ListMultimap | Guava | ImmutableListMultimap |
SetMultimap | Guava | ImmutableSetMultimap |
BiMap | Guava | ImmutableBiMap |
ClassToInstanceMap | Guava | ImmutableClassToInstanceMap |
Table | Guava | ImmutableTable |
ImmutableXXX只能作到對集合對應的不可編輯,集合內的對象並無真進行復制,若是對內部對象進行修改,仍是會連鎖生效,好比下面的代碼會輸出100。反之亦然。
JavaBean bean= new JavaBean (0); List<JavaBean > list = new ArrayList<JavaBean >(); list.add(bean); ImmutableList<JavaBean > copyOf = ImmutableList.copyOf(list); JavaBean bean2 = copyOf.get(0); bean.setId(100); System.out.println(bean2.getId());