鍍金池/ 教程/ Java/ 不可變集合
不可變集合
排序: Guava 強大的”流暢風(fēng)格比較器”
強大的集合工具類:java.util.Collections 中未包含的集合工具
新集合類型
常見 Object 方法
I/O
前置條件
字符串處理:分割,連接,填充
散列
原生類型
數(shù)學(xué)運算
使用和避免 null
Throwables:簡化異常和錯誤的傳播與檢查
google Guava 包的 ListenableFuture 解析
事件總線
緩存
函數(shù)式編程
區(qū)間
集合擴展工具類
Google-Guava Concurrent 包里的 Service 框架淺析
google Guava 包的 reflection 解析

不可變集合

范例


    public static final ImmutableSet<String> COLOR_NAMES = ImmutableSet.of(
            "red",
            "orange",
            "yellow",
            "green",
            "blue",
            "purple");

    class Foo {
        Set<Bar> bars;
        Foo(Set<Bar> bars) {
            this.bars = ImmutableSet.copyOf(bars); // defensive copy!
        }
    }

為什么要使用不可變集合

不可變對象有很多優(yōu)點,包括:

  • 當(dāng)對象被不可信的庫調(diào)用時,不可變形式是安全的;
  • 不可變對象被多個線程調(diào)用時,不存在競態(tài)條件問題
  • 不可變集合不需要考慮變化,因此可以節(jié)省時間和空間。所有不可變的集合都比它們的可變形式有更好的內(nèi)存利用率(分析和測試細節(jié));
  • 不可變對象因為有固定不變,可以作為常量來安全使用。

創(chuàng)建對象的不可變拷貝是一項很好的防御性編程技巧。Guava 為所有 JDK 標(biāo)準(zhǔn)集合類型和 Guava 新集合類型都提供了簡單易用的不可變版本。

JDK 也提供了 Collections.unmodifiableXXX 方法把集合包裝為不可變形式,但我們認為不夠好:

  • 笨重而且累贅:不能舒適地用在所有想做防御性拷貝的場景;
  • 不安全:要保證沒人通過原集合的引用進行修改,返回的集合才是事實上不可變的;
  • 低效:包裝過的集合仍然保有可變集合的開銷,比如并發(fā)修改的檢查、散列表的額外空間,等等。

如果你沒有修改某個集合的需求,或者希望某個集合保持不變時,把它防御性地拷貝到不可變集合是個很好的實踐。

重要提示:所有 Guava 不可變集合的實現(xiàn)都不接受 null 值。我們對 Google 內(nèi)部的代碼庫做過詳細研究,發(fā)現(xiàn)只有 5%的情況需要在集合中允許 null 元素,剩下的 95%場景都是遇到 null 值就快速失敗。如果你需要在不可變集合中使用 null,請使用 JDK 中的 Collections.unmodifiableXXX 方法。更多細節(jié)建議請參考“使用和避免 null”。

怎么使用不可變集合

不可變集合可以用如下多種方式創(chuàng)建:

  • copyOf 方法,如 ImmutableSet.copyOf(set);
  • of 方法,如 ImmutableSet.of(“a”, “b”, “c”)或 ImmutableMap.of(“a”, 1, “b”, 2);
  • Builder 工具,如

    public static final ImmutableSet<Color> GOOGLE_COLORS =
            ImmutableSet.<Color>builder()
                .addAll(WEBSAFE_COLORS)
                .add(new Color(0, 191, 255))
                .build();

此外,對有序不可變集合來說,排序是在構(gòu)造集合的時候完成的,如:


    ImmutableSortedSet.of("a", "b", "c", "a", "d", "b");

會在構(gòu)造時就把元素排序為 a, b, c, d。

比想象中更智能的 copyOf

請注意,ImmutableXXX.copyOf 方法會嘗試在安全的時候避免做拷貝——實際的實現(xiàn)細節(jié)不詳,但通常來說是很智能的,比如:


    ImmutableSet<String> foobar = ImmutableSet.of("foo", "bar", "baz");
    thingamajig(foobar);

    void thingamajig(Collection<String> collection) {
        ImmutableList<String> defensiveCopy = ImmutableList.copyOf(collection);
        ...
    }

在這段代碼中,ImmutableList.copyOf(foobar)會智能地直接返回 foobar.asList(),它是一個 ImmutableSet 的常量時間復(fù)雜度的 List 視圖。

作為一種探索,ImmutableXXX.copyOf(ImmutableCollection)會試圖對如下情況避免線性時間拷貝:

  • 在常量時間內(nèi)使用底層數(shù)據(jù)結(jié)構(gòu)是可能的——例如,ImmutableSet.copyOf(ImmutableList)就不能在常量時間內(nèi)完成。
  • 不會造成內(nèi)存泄露——例如,你有個很大的不可變集合 ImmutableList hugeList, ImmutableList.copyOf(hugeList.subList(0, 10))就會顯式地拷貝,以免不必要地持有 hugeList 的引用。
  • 不改變語義——所以 ImmutableSet.copyOf(myImmutableSortedSet)會顯式地拷貝,因為和基于比較器的 ImmutableSortedSet 相比,ImmutableSet對hashCode()和 equals 有不同語義。

在可能的情況下避免線性拷貝,可以最大限度地減少防御性編程風(fēng)格所帶來的性能開銷。

asList視圖

所有不可變集合都有一個 asList()方法提供 ImmutableList 視圖,來幫助你用列表形式方便地讀取集合元素。例如,你可以使用 sortedSet.asList().get(k)從 ImmutableSortedSet 中讀取第 k 個最小元素。

asList()返回的 ImmutableList 通常是——并不總是——開銷穩(wěn)定的視圖實現(xiàn),而不是簡單地把元素拷貝進 List。也就是說,asList 返回的列表視圖通常比一般的列表平均性能更好,比如,在底層集合支持的情況下,它總是使用高效的 contains 方法。

細節(jié):關(guān)聯(lián)可變集合和不可變集合

可變集合接口 屬于JDK還是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