1. 程式人生 > >[Google Guava] 2.1-不可變集合

[Google Guava] 2.1-不可變集合

原文連結 譯者:沈義揚

範例

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!
    }
}

為什麼要使用不可變集合

不可變物件有很多優點,包括:

  • 當物件被不可信的庫呼叫時,不可變形式是安全的;
  • 不可變物件被多個執行緒呼叫時,不存在競態條件問題
  • 不可變集合不需要考慮變化,因此可以節省時間和空間。所有不可變的集合都比它們的可變形式有更好的記憶體利用率(分析和測試細節);
  • 不可變物件因為有固定不變,可以作為常量來安全使用。

建立物件的不可變拷貝是一項很好的防禦性程式設計技巧。Guava為所有JDK標準集合型別和Guava新集合型別都提供了簡單易用的不可變版本。
 JDK也提供了Collections.unmodifiableXXX方法把集合包裝為不可變形式,但我們認為不夠好:

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

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

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

“使用和避免null”

怎麼使用不可變集合

不可變集合可以用如下多種方式建立:

  • 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();

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

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

會在構造時就把元素排序為a, b, c, d。

比想象中更智慧的copyOf

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

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的常量時間複雜度的List檢視。
作為一種探索,ImmutableXXX.copyOf(ImmutableCollection)會試圖對如下情況避免線性時間拷貝:

  • 在常量時間內使用底層資料結構是可能的——例如,ImmutableSet.copyOf(ImmutableList)就不能在常量時間內完成。
  • 不會造成記憶體洩露——例如,你有個很大的不可變集合ImmutableList<String>
    hugeList, ImmutableList.copyOf(hugeList.subList(0, 10))就會顯式地拷貝,以免不必要地持有hugeList的引用。
  • 不改變語義——所以ImmutableSet.copyOf(myImmutableSortedSet)會顯式地拷貝,因為和基於比較器的ImmutableSortedSet相比,ImmutableSet對hashCode()和equals有不同語義。

在可能的情況下避免線性拷貝,可以最大限度地減少防禦性程式設計風格所帶來的效能開銷。

asList檢視

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

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

細節:關聯可變集合和不可變集合