本章主要介绍Guava框架给我们带来的有关集合的系列工具类和新的集合类型。

第一节 不可变的集合们

关于不可变的概念和好处,请看这篇文章:{% post_link effective-java-3 %} Guava为我们提供了一系列的不可变集合类,每个都有方便地方法来获得实例。下面先罗列出它们:

Untitled

其中,后面的一些集合是Guava新增加的,在后面的小节会有介绍。 可以看到,每个集合类都有其对应的不可变版本。 ### 创建不可变集合的方法 如果构造上面罗列的不可变集合呢,有三种方法: - 使用copyOf静态方法,可以直接从一个现有的集合中构造出不可变的集合,例如:ImmutableSet.copyOf(set) - 使用of静态方法,可以直接传入集合元素来构造不可变的集合,例如:ImmutableSet.of("a", "b", "c") - 使用Builder构造器,例如:

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

聪明的copyOf方法

在上面提到的方法中,copyOf方法非常聪明,它会在允许的情况下,尽可能地不复制数据到产生的不可变集合中,这样可以在某些场景下提高性能。 比如:

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

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

在这个例子里,copyOf会调用foobar.asList()方法,产生一个原始集合的视图,而不是拷贝数据,访问视图实际上是在访问原始集合,但是由于视图是不可变的,所以不会有修改数据的麻烦。 另外,每个不可变集合都有一个asList()方法来产生一个不可变的List视图,使用这种方法,能够提高程序的性能,略去拷贝的过程,而在调用contains这种方法时,会调用原始集合的对应方法,如果原始集合是个Set,那么这种查询是常数级时间。

第二节 新的集合类型们

Guava为我们提供了一系列新的集合类,用来扩展集合框架的功能,官方文档说,他们新写的这些集合类型都能非常好地与JDK原有的类型和平相处,绝对不会硬塞进一些不和谐的东西到集合框架中。 那么让我们看看都有些什么新东西。 ### Multiset 官方给了下面这个例子:

Map<String, Integer> counts = new HashMap<String, Integer>();
for (String word : words) {
  Integer count = counts.get(word);
  if (count == null) {
    counts.put(word, 1);
  } else {
    counts.put(word, count + 1);
  }
}

可以看出,这是一段计算一句话中每个单词出现次数的代码。这种方式非常笨重,而且不能支持更多的统计,比如一共有多少个单词等。 #### Multiset是什么 Multiset顾名思义,就是可以放进同一个元素多次的集合,类似于元组,元素的顺序是无关紧要的,比如{a,a,b}{a,b,a}是相等的。 它即像一个顺序无所谓的ArrayList,又像一个可以统计元素出现次数的Map<E,Integer>。 当我们把它当成一个普通的集合的时候,即用做无顺序的ArrayList时: - 可以使用add(E)方法来添加一个元素(可以重复添加) - 可以使用iterator()来获得迭代器去迭代集合中的每个元素(如果重复会迭代多次) - 可以使用size()来统计所有的元素出现的所有的次数,即重复的会算多次。

除了普通集合的功能,它还有一些额外的功能: - 可以使用count(Object)来返回元素出现在集合中多少次,对于HashMultiset实现来说,这个操作时间复杂度是O(1),对于TreeMultiset来说,是O(logn),以此类推。 - 可以使用entrySet()来返回一个Set<Multiset.Entry<E>>,就像Map里面的那个Entry一样,使用这个Entry可以很方便地得到每个元素以及元素出现的次数。 - 可以使用elementSet()来返回一个Set<E>,这个Set包含所有的去重之后的元素。 - 所有Multiset实现空间复杂度都是O(n),其中,n代表去重后的元素个数

Multiset不是Map,虽然有点儿像

注意,Multiset不是一个Map,虽然它的有些方法和内部实现比较像Map,但是它是实打实的Collection,只有以下一个特别需要注意的地方: - 所有的元素都有一个正的counts来表示元素出现了几次,这个counts绝对不会是负数,如果为0就认为该元素不在Multiset内,因此elementSet()也不会返回该元素 - size()方法返回的是所有的元素的counts的总和,如果你想要得到所有的元素的去重后的个数,使用elementSet().size()来得到 - iterator()返回的迭代器会迭代重复的元素多次 - Multiset支持添加元素,删除元素,还支持直接设置元素出现的次数,setCount(elem,0)等于直接将元素删除 - count(elem)会返回0,如果传入的元素不在Multiset中的话

多种实现

前面说了,Multiset不过是一个接口,guava为我们提供了一系列实现,如下: - HashMultiset,支持null元素 - TreeMultiset,支持null元素(如果comparator比较器支持的话) - LinkedHashMultiset,支持null元素 - ConcurrentHashMultiset,不支持null元素 - ImmutableMultiset,不支持null元素

除了上面一些常用的实现以外,还有一些不常用的实现,比如SortedMultiset这样的实现,就不详述了,具体可以参照官方API文档。