(Guava 译文系列)不可变集合

不可变集合

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static final ImmutableSet<String> COLOR_NAMES = ImmutableSet.of(
"red",
"orange",
"yellow",
"green",
"blue",
"purple");

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

为何要这么做?

不可变对象有非常多的优点,包括: - 安全的被不受信任的库使用。 - 线程安全:能够被许多线程使用且不存在竞态条件的风险。 - 不需要支持变更,基于此假设,能够节省时间和空间。所有的不可变集合的实现都比他们可变集合兄弟的内存效率要高。(分析) - 可以当做常量使用,能够期望他会保持固定不变

为对象创建不可变副本是一项优秀的防御式编程技术。Guava 为标准Collection类型提供了简单、易于使用的不可变版本,包括 Guava 自己的Collection 变体。

JDK 提供了Collections.unmodifiableXXX方法,但在我们看来,它: - 笨重且冗长;在任何你想要做防御副本的地方使用都令人不快 - 不安全:只有当没有人持有原集合的引用时,才能会返回真正的不可变集合 - 低效:数据结构仍然包含了可变集合的所有开销,包括并发修改检查,哈希表所需的额外空间等等

当你不期望修改一个集合,或期望一个集合保持不变时,一个不错的实践是将之防御性的拷贝入一个不可变的集合。

重要:所有 Guava 的不可变集合实现都拒绝 null 值。基于我们对 Google 代码库的彻底研究表明,只有 5% 的情况下,null被集合所接受,其余 95% 的情况都受益于对null快速失败。假如你需要使用null,那就考虑使用Collections.unmodifiableList或类似的允许null值的实现吧。

如何使用?

一个ImmutableXXX集合可以通过以下几种方式创建: - 使用copyOf方法,如ImmutableSet.copyOf(set) - 使用of方法,如ImmutableSet.of("a", "b", "c")ImmutableMap.of("a", 1, "b", 2) - 使用Builder,如

1
2
3
4
5
public static final ImmutableSet<Color> GOOGLE_COLORS =
ImmutableSet.<Color>builder()
.addAll(WEBSAFE_COLORS)
.add(new Color(0, 191, 255))
.build();
除了已经排序的集合外,集合顺序会在构造时被确定。如 ImmutableSet.of("a", "b", "c", "a", "d", "b") 的元素会以"a","b","c","d"的顺序被遍历。

copyOf比你想象的更聪明

很有用的一点是,请记住ImmutableXXX.copyOf会在安全的前提下尽量避免复制数据 -- 具体细节未明,但实现通常很“智能”。例如:

1
2
3
4
5
6
7
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的常量耗时视图。

作为一般性的启发,ImmutableXXX.copyOf(ImmutableCollection)尝试在下述时刻避免进行线性耗时拷贝: - 有可能在常量时间内使用底层数据结构时。如ImmutableSet.copyOf(ImmutableList)则无法在常量时间内完成。 - 不会造成内存泄漏时。例如,假定你有一个ImmutableList<String> hugeList,然后你执行了ImmutableList.copyOf(hugeList.subList(0, 10)),则显示复制会被执行,这能够避免意外的持有hugeList的引用,这显然并无必要。 - 不会改变语义时。因此ImmutableSet.copyOf(myImmutableSortedSet)会执行显示拷贝,因为ImmutableSet所持有的hashCode()equals()与基于比较行为的ImmutableSortedSet的语义并不相同。

这能帮助最小化防御式编程风格所造成的性能开销。

asList

所有不可变集合都能通过asList()来提供ImmutableList的视图,所以,例如你有一个ImmutableSortedSet,则你可以通过sortedSet.asList().get(k)来获取第k小的元素。

返回的ImmutableList通常 -- 不总是但通常 -- 是一个常量开销的视图,而不是显示拷贝的。这就是说,它通常比普通的List更聪明,例如他会用原有集合的更高效的contains方法。

详细

在哪儿?

Interface JDK or Guava? Immutable Version
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