Guava官方文档中文版(二)-集合

集合(Collections)

不可变集合(Immutable Collections)

示例

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提供简单的,易于使用的每一个标准Collectioin类型的不可变版本,还包括Guava自己的Collection变体。

JDK提供Collections.unmodifiableXXX方法,但是以我们来看,存在:

  • 笨重和冗余;在你想要使用保护复制的地方使用起来不愉快。
  • 不安全:只有当没有人持有原始集合的y引用时,返回的集合才是真正的不可变。
  • 效率低:数据结构仍然有所有可变集合的开销,包括多线程修改检查,在hash table中的额外空间,等等。

当你不希望修改一个集合,或者期望集合保持一致,将他保护性复制到一个不可变集合是一个很好的实践。

重点:每个Guava不可变集合实现拒绝null值。我们在Google的内部代码基础上做了一个详细的调查,表明在集合中允许null元素大约占5%时间,其他95%的情况最好通过快速失败处理null。如果你需要使用null值,考虑使用Collections.unmodifiableList和在集合实现上的其他集合允许null。更多详细的建议可以在这里找到。

如何使用

可以通过几种方式创建ImmutableXXX集合:

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

除了排序的集合之外,在构造时将保持排序。例如:

ImmutableSet.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的一个常数时间视图。

作为一般的启发式,如果有以下情况,ImmutableXXX.copyOf(ImmutableCollection)尝试避免线性时间复制:

  • 在常数时间内使用底层数据结构是可能的。例如,ImmutableSet.copyOf(ImmutableList)不能在常数时间内完成。
  • 它不会导致内存泄露。–例如,如果你有ImmutableList<String> hugeList,并且你要ImmutableList.copyOf(hugeList.subList(0, 10)),执行显式复制,以避免意外持有hugeList中不需要的引用。
  • 它不会修改语义。–ImmutableSet.copyOf(myImmutableSortedSet)将执行一个显性复制,因为ImmutableSet使用的hashCode()equalsImmutableSortedSet基于比较器行为有不同的语义。

这将帮助良好的保护性程序风格的性能消耗减到最少程度。

asList

所有的不可变集合通过asList提供了一个ImmutableList视图。例如,即使你有数据按照ImmutableSortedSet进行存储,你可以使用sortedSet.asList().get(k)得到第k个最小元素。

返回ImmutableList是经常的(不一直是,但是经常)是一个常量开销视图,而不是显性复制。也就是说,它通常比一般的List更聪明,–例如,你将使用备份集合的有效的contains方法

明细

在哪?
接口JDK 或者Guava不变的版本
CollectionJDKImmutableCollection
ListJDKImmutableList
SetJDKImmutableSet
SortedSet NavigableSetJDKImmutableSortedSet
MapJDKImmutableMap
SortedMapJDKImmutableSortedMap
MultisetGuavaImmutableMultiset
SortedMultisetGuavaImmutableSortedMultiset
MultimapGuavaImmutableMultimap
ListMultimapGuavaImmutableListMultimap
SetMultimapGuavaImmutableSetMultimap
BiMapGuavaImmutableBiMap
ClassToInstanceMapGuavaImmutableClassToInstanceMap
TableGuavaImmutableTable

新的集合类型

Guava引入了多个在JDK中没有的新的集合类型,而且我们已经发现有广泛的用处。这些被设计为适合与JDK集合框架共存,没有牵强的东西放入到JDK集合抽象中。

作为一般规则,Guava集合实现非常精确地遵循JDK接口契约。

Multiset

传统的java习惯,例如计算在文档中出现相同单词的次数,就像:

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);
  }
}

这很尴尬,容易出错,而且不支持收集多种有用的统计,像单词的个数。我们可以做的更好。

Guava提供了一个新的集合类型,Multiset,它支持添加多个元素。Wikipedia在数学领域定义了一个multiset,“set的概念泛化,在它的成员中允许出现多次,在multisets中,就像在集合中一样,与元组相反,元素的顺序是不相干的:multisets {a, a, b}与{a, b, a}相等”。

有两种方式看待这个问题:

  • 就像ArrayList<E>,没有顺序限制:排序不关系。
  • 就像Map<E, Integer>,有元素和计数。

Guava的Multiset API结合了考虑了Multiset的两种方式,如下:

  • 当视为一个普通Collection时,Multiset行为更像是一个未排序的ArrayList:
    • 调用add(E)添加给定元素的一次出现。
    • Multiset的iterator()遍历每一个元素的每一次出现。
    • Multiset的size()是所有元素所有出现的总个数。
  • 额外的查询操作,以及性能特点,就像Map<E, Integer>你期望的那样:
    • count(Object)返回与该元素关联的数量。对于HashMultiset,count是O(1),对于TreeMultiset,count是O(log n)等等。
    • entrySet()返回Set<Multiset.Entry<E>>其工作类似于Map的entrySet。
    • elementSet()返回multiset不同元素的Set<E>,像entrySet()用于Map一样。
    • Multiset实现的内存消耗是按照不同元素的个数线性的。

特别是,MultisetCollection接口的契约完全一致,除非在JDK本身有极少先例的情况下,–特别是,TreeMultiset,就像TreeSet,使用比较来表示相等,而不是使用Object.equals。特别是,Multiset.addAll(Collection)为每一个元素在Collection中的每次出现的添加一次出现次数,它比上面Map方法需要的for循环更加方便。

方法描述
count(E)已经添加到此multiset元素的出现次数的计数个数
elementSet()Multiset<E>作为Set<E>展示不同的元素
entrySet()类似于Map.entrySet(),返回Set<Multiset.Entry<E>>,包含条目支持getElement()getCount()
add(E, int)添加指定元素的出现次数的指定数量
remove(E, int)移除指定元素的出现次数的指定数量
setCount(E, int)设置指定元素的出现个数为非负数值
size()返回在Multiset中所有元素的出现个数的总数

Multiset不是一个Map

注意,Multiset<E>不是Map<E, Integer>,尽管这个是Multiset实现的一部分。Multiset是一个真Collection类型,并且满足所有关联的契约义务。其他显著差异的包括:

  • Multiset<E>只有正数的元素。没有元素可以有负数个数,计数为0的值被认为不会在multiset中。他们不会出现在elementSet()entrySet()视图中。
  • multiset.size()返回集合的大小。其与所有元素个数的总和。对于不同的元素的数量,使用elementSet().size()。(所以,例如,add(E)增加multiset.size()一次)。
  • multiset.iterator()在每个元素的每次出现之上迭代,所以这个迭代的长度等于multiset.size()
  • Multiset<E>支持添加元素,移除元素,或者直接设置元素的个数。setCount(elem, 0)是相当于移除元素的所有出现次数。
  • multiset.count(elem)对于在multiset中的元素不会返回0

实现

Guava提供许多Multiset实现,大致与JDK map实现相同。

Map对应的Multiset支持null元素
HashMapHashMultiset
TreeMapTreeMultiset
LinkedHashMapLinkedHashMultiset
ConcurrentHashMapConcurrentHashMultiset
ImmutableMapImmutableMultiset

SortedMultiset

SortedMultiset是在Multiset接口上的变体,其支持在特定的范围内高效地获取子Multiset。例如,你可以使用latencies.subMultiset(0, BoundType.CLOSED, 100, BoundType.OPEN).size()确定多少次点击你的站点在100ms延迟以下,并且与latencies.size()比较确定完整比例。

TreeMultiset实现SortedMultiset接口。在书写本文时,ImmutableSortedMultiset仍在测试GWT功能的兼容性。

Multimap

每一个有经验的Java程序员已经在某些时候实现一个Map<K, List<V>>或者Map<K, Set<V>>,并且处理了该结构的尴尬。例如,Map<K, Set<V>>是一个典型的方式来表示无标记有向图。Guava的Multimap框架更容易处理key和多值的映射。Multimap是一个普通的方式来将key与任意地许多值关联。

有两种方式考虑Multimap概念:作为从单个key映射到单个值映射的集合:

a -> 1
a -> 2
a -> 4
b -> 3
c -> 5

或者作为单一key到集合值的映射:

a -> [1, 2, 4]
b -> [3]
c -> [5]

一般来说,根据第一视图考虑Multimap是最好的,但是允许你使用asMap()视图这种方式查看他,它返回Map<K, Collection<V>>。最重要的是,没有任何东西作为key映射到空集合:key至少映射一个值,或者它只是在Multimap中不存在。

你几乎很少直接使用Multimap接口,但是,你经常使用ListMultimap或者SetMultimap,其key依次映射List或者Set

构造

创建Multimap最直接了当的方式是使用MultimapBuilder,它允许你配置你的key和value应该如何展现。例如:

// creates a ListMultimap with tree keys and array list values
ListMultimap<String, Integer> treeListMultimap =
    MultimapBuilder.treeKeys().arrayListValues().build();

// creates a SetMultimap with hash keys and enum set values
SetMultimap<Integer, MyEnum> hashEnumMultimap =
    MultimapBuilder.hashKeys().enumSetValues(MyEnum.class).build();

你也可以选择在实现类上直接使用create()方法,当时相当于MultimapBuilder,不鼓励这么做。

修改

Multimap.get(key)返回一个与指定key关联的值的视图,即使当前是空的。对于ListMultimap,它返回List,对于SetMultimap,它返回Set.

修改会写入底层的Multimap。例如:

Set<Person> aliceChildren = childrenMultimap.get(alice);
aliceChildren.clear();
aliceChildren.add(bob);
aliceChildren.add(carol);

写入底层multimap。

其他方式修改multimap(更直接)包含:

签名描述等价方法
put(K, V)添加key指向value的关联multimap.get(key).add(value)
putAll(K, Iterable<V>)依次添加key指向value的关联Iterables.addAll(multimap.get(key), values)
remove(K, V)移除key指向的value的关联,如果multimap有变化则返回truemultimap.get(key).remove(value)
removeAll(K)移除并返回与指定key关联的值。返回集合可能不可修改,但是修改它不会影响multimap。(返回合理的集合类型)multimap.get(key).clear()
replaceValues(K, Iterable<V>)清除所有与key关联的值并将key设置为关联每一个values。返回之前与key关联的值。multimap.get(key).clear(); Iterables.addAll(multimap.get(key), values)

视图(View)

Multimap也支持多个强大的视图。

  • asMap将任何Multimap<K, V>转为Map<K, Collection<V>>视图。返回的map支持remove,并且返回的集合修改将写入,但是map不支持putputAll。关键的是,当你在不存在的key想要null,而不是一个新的、可写入的空集合时,你可以使用asMap().get(key)。(你可以并应该将asMap.get(key)转为合适的集合类型 – Set对应SetMultimap,List对应ListMultimap – 但是在这里类型系统不允许ListMultimap不允许返回Map<K, List<V>>)。
  • entries展示在Multimap中的所有条目的Collection<Map.Entry<K, V>>.(对于SetMultimap,即为Set。)
  • keySet将在Multimap中的不同的key作为Set展示
  • keysMultimap的key作为Multiset展示,多重性等于关联到该key的值的数量。元素可以从Multiset移除,但不能添加;修改将写入。
  • values()将在Multimap中的所有值作为铺平的Collection<V>展示,所有的都作为一个集合。这类似于Iterables.concat(multimap.asMap().values()),但是返回一个完整的Collection

Multimap不是一个map

Multimap<K, V>不是Map<K, Collection<V>>,尽管这样的map可能用于Multimap实现。显著差异包括:

  • Multimap.get(key)总是返回非null,可能为空的集合。这不意味着multimap花费任何与key关联的内存,但是相反地,返回的集合是一个视图,它允许你根据需要添加key的关联。
  • 如果对于没有在multimap中的key,你更喜欢像Map行为那样返回null,使用asMap()视图来获取Map<K, Collection<V>>(或者从ListMultimap中获取Map<K,List<V>>,使用静态的Multimaps.asMap()方法。类似于存在于SetMultimapSortedSetMultimap的方法)。
  • 如果存在与指定key关联的任何元素,Multimap.containsKey(key)为true。特别是,如果一个key,与一个或者多个值关联k已经从multimap移除,Multimap.containsKey(k)将返回false。
  • Multimap.entries()将返回在Multimap中所有key的所有条目。如果你想要所有key集合条目,使用asMap().entrySet()
  • Multimap.size()返回整个multimap中的条目个数,不是不同key的个数。使用Multimap.keySet().size()代替来获取不同key的个数。

实现

Multimap提供大量多种实现。注意通常更喜欢使用MultimapBuilder创建Multimap实例。

实现key行为像…value行为像…
ArrayListMultimapHashMapArrayList
HashMultimapHashMapHashSet
LinkedListMultimap *LinkedHashMap,*LinkedList ,*
LinkedHashMultimap **LinkedHashMapLinkedHashSet
TreeMultimapTreeMapTreeSet
ImmutableListMultimapImmutableMapImmutableList
ImmutableSetMultimapImmutableMapImmutableSet

除了immutable实现,其他每一个实现支持null key和value。

* LinkedListMultimap.entries()保留非不同键值之间的迭代顺序。查看链接了解详情。

** LinkedHashMultimap保留条目的插入顺序,以及保留key和与任意一个key关联的值的set的插入顺序。

请注意,并不是所有的实现实际上按照所列出来的实现实现Map<K, Collection<V>>。(特别是,几种Multimap实现使用自定义的hash table来是消耗最小化)。

如果你需要自定义,使用Multimaps.newMultimap(Map, Supplier<Collection>)或者list或者set版本使用一个自定义集合,列表或者set实现来返回你的multimap。

BiMap

将值映射回key传统的方式是维护两个独立的map,并使他们保持同步,但是当一个值已经存在map中时,这样易于造成bug,非常容易混乱。例如:

Map<String, Integer> nameToId = Maps.newHashMap();
Map<Integer, String> idToName = Maps.newHashMap();

nameToId.put("Bob", 42);
idToName.put(42, "Bob");
// what happens if "Bob" or 42 are already present?
// weird bugs can arise if we forget to keep these in sync...

BiMap<K, V>是一个map:

  • 允许使用inverse()你查看“相反的” BiMap<V, K>
  • 确保值是唯一的,设置values()为一个Set

如果你尝试将一个key映射到已存在的值,BiMap.put(key, value)将抛出IllegalArgumentException。如果你希望使用特定的值删除任何已存在的条目,使用BiMap.forcePut(key, value)代替。

BiMap<String, Integer> userId = HashBiMap.create();
...

String userForId = userId.inverse().get(id);

实现

Key-Value Map ImplValue-Key Map Impl相应的BiMap
HashMapHashMapHashBiMap
ImmutableMapImmutableMapImmutableBiMap
EnumMapEnumMapEnumBiMap
EnumMapHashMapEnumHashBiMap

注意:BiMap实用工具像在MapssynchronizedBiMap

表(Table)

Table<Vertex, Vertex, Double> weightedGraph = HashBasedTable.create();
weightedGraph.put(v1, v2, 4);
weightedGraph.put(v1, v3, 20);
weightedGraph.put(v2, v3, 5);

weightedGraph.row(v1); // returns a Map mapping v2 to 4, v3 to 20
weightedGraph.column(v3); // returns a Map mapping v1 to 20, v2 to 5

通常,当你尝试在某时索引超过一个key时,你将会得到Map<FirstName, Map<LastName, Person>>,其是丑陋的和使用不便。Guava提供一个新的集合类型,Table,它支持任何“row”类型和“column”类型的这种情况。Table支持多种视图让你从多种角度使用数据,包括:

  • rowMap(),其展示一个Table<R, C, V>作为Map<R, Map<C, V>>。类似于,rowKeySet()返回一个Set<R>
  • row(r) 返回一个非null的Map<C, V>。写入这个Map将会写入底层的Table
  • 提供类似列方法:columnMap()columnKeySet()column(c)。(基于列访问比基于行访问效率稍微低一点)
  • cellSet()返回一个Table视图,正如Table.Cell<R, C, V>集合。Cell比较像Map.Entry,但是区分行键和列键。

提供几个Table实现,包括:

  • HashBasedTable,本质上通过HashMap<R, HashMap<C, V>>支持。
  • TreeBasedTable,本质上通过TreeMap<R, TreeMap<C, V>>支持。
  • ImmutableTable
  • ArrayTable,其要求在构造时指定完整的行和列,当数据密集时,通过两个维度数组支持来提升速度和内存效率。ArrayTable工作方式与其他实现有些不同;查阅Javadoc了解更多详情。

ClassToInstanceMap

有时,你映射的key不是所有相同的类型:他们是类型,而且你想要将他们映射到此类型的值。Guava提供ClassToInstanceMap达到此目的。

除了扩展Map接口,ClassToInstanceMap提供方法T getInstance(Class<T>)T putInstance(Class<T>, T),这样消除了令人不快的转换的需要,同时加强类型安全。

ClassToInstanceMap有一个单独的类型参数,通常称为B,表示map管理的上界类型。例如:

ClassToInstanceMap<Number> numberDefaults = MutableClassToInstanceMap.create();
numberDefaults.putInstance(Integer.class, Integer.valueOf(0));

技术上,ClassToInstanceMap<B>实现Map<Class<? extends B>, B> – 换句话说,将B的子类映射到B的实例。这可能使在ClassToInstanceMap中的泛型调用稍显混乱,但是仅记住B总是map中的上界类型 – 通常,B只是Object

Guava提供有帮助的实现,名为MutableClassToInstanceMapImmutableClassToInstanceMap

主要:像其他Map<Class, Object>ClassToInstanceMap可能包含原生类型条目,并且原生类型和它相对应的封装类型可能映射到不同的值。

RangeSet

RangeSet描述了一组不连接的,非空的范围。当添加一个范围到可变RangeSet时,任何连接的范围被合并到一起,而忽略空的范围。例如:

   RangeSet<Integer> rangeSet = TreeRangeSet.create();
   rangeSet.add(Range.closed(1, 10)); // {[1, 10]}
   rangeSet.add(Range.closedOpen(11, 15)); // disconnected range: {[1, 10], [11, 15)}
   rangeSet.add(Range.closedOpen(15, 20)); // connected range; {[1, 10], [11, 20)}
   rangeSet.add(Range.openClosed(0, 0)); // empty range; {[1, 10], [11, 20)}
   rangeSet.remove(Range.open(5, 10)); // splits [1, 10]; {[1, 5], [10, 10], [11, 20)}

注意:合并像Range.closed(1, 10)Range.closedOpen(11, 15)的范围,你必须先使用Range.canonical(DiscreteDomain)进行预处理,例如使用DiscreteDomain.integers()

**注意:**GWT和JDK1.5后端端口都不支持RangeSetRangeSet需要充分利用JDK1.6 中的NavigableMap的特性。

视图

RangeSet实现支持极其广的范围视图,包含:

  • complement():查看RangeSet的补充。complement也是一个RangeSet,因为它包含不连接的,非空的范围。
  • subRangeSet(Range<C>):返回RangeSet与指定的Range的交叉点视图。这样通用了传统排序集合的headSetsubSettailSet视图。
  • asRanges():将RangeSet作为可被迭代的Set<Range<C>>查看。
  • asSet(DiscreteDomain<C>)(仅仅ImmutableRangeSet ):将RangeSet<C>作为ImmutableSortedSet<C>查看,查看在范围内的元素而不是范围本身。(如果DiscreteDomainRangeSet上下都是无界的,此操作是不支持的。)
查询

除了在它的视图上操作,RangeSet直接支持几个查询操作,最突出的是:

  • contains(C)RangeSet上最基本的操作,查询在RangeSet中任意范围是否包含指定元素。
  • rangeContaining(C):返回Range,其封装指定的元素,如果没有则返回null
  • encloses(Range<C>):够直接,测试在RangeSet中的任意Range是否封装指定的范围。
  • span():返回在此RangeSetencloses每一个范围的最小的Range

RangeMap

RangeMap是一个集合类型描述从不连贯,非空范围对值的映射。不像RangeSetRangeMap从不"合并"相邻的映射,尽管相邻的范围映射到相同的值。例如:

RangeMap<Integer, String> rangeMap = TreeRangeMap.create();
rangeMap.put(Range.closed(1, 10), "foo"); // {[1, 10] => "foo"}
rangeMap.put(Range.open(3, 6), "bar"); // {[1, 3] => "foo", (3, 6) => "bar", [6, 10] => "foo"}
rangeMap.put(Range.open(10, 20), "foo"); // {[1, 3] => "foo", (3, 6) => "bar", [6, 10] => "foo", (10, 20) => "foo"}
rangeMap.remove(Range.closed(5, 11)); // {[1, 3] => "foo", (3, 5) => "bar", (11, 20) => "foo"}
视图

RangeMap提供两个视图:

  • asMapOfRanges():将RangeMap作为Map<Range<K>, V>展示。例如,这可以用于迭代RangeMap
  • subRangeMap(Range<K>):将RangeMap与指定的Range的焦点作为RangeMap展示。通用了传统的headMapsubMaptailMap操作。

工具类

任何使用JDK集合框架有经验的程序员知道并喜欢在java.util.Collections可用工具。Guava在这方便提供更多的实用工具:适用于所有集合的静态方法。这些是Guava最受欢迎和最成熟的部分。

对应于特定接口的方法以相对直观的方式分组:

接口JDK 或者Guava对应的Guava工具类
CollectionJDKCollections2
ListJDKLists
SetJDKSets
SortedSetJDKSets
MapJDKMaps
SortedMapJDKMaps
QueueJDKQueues
MultisetGuavaMultisets
MultimapGuavaMultimaps
BiMapGuavaMaps
TableGuavaTables

寻找转换过滤器等等?这些内容在我们的函数式编程章节,函数式语法下。

静态构造器

在JDK7之前,构造新的泛型集合需要令人不太愉快的代码复制:

List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<TypeThatsTooLongForItsOwnGood>();

我想我们都会同意这是不愉快的。Guava提供静态方法使用泛型推断在右侧的类型:

List<TypeThatsTooLongForItsOwnGood> list = Lists.newArrayList();
Map<KeyType, LongishValueType> map = Maps.newLinkedHashMap();

可以肯定的是,在JDK7的中的菱形操作符减少了这种麻烦:

List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<>();

但是Guava比这个更进一步。使用工厂方法模式,我们可以非常方便的使用他们开始的元素初始化集合。

Set<Type> copySet = Sets.newHashSet(elements);
List<String> theseElements = Lists.newArrayList("alpha", "beta", "gamma");

除此之外,通过命名工厂方法的能力(Effective Java item 1),我们可以提升初始化集合大小的阅读性:

List<Type> exactly100 = Lists.newArrayListWithCapacity(100);
List<Type> approx100 = Lists.newArrayListWithExpectedSize(100);
Set<Type> approx100Set = Sets.newHashSetWithExpectedSize(100);

下面列出了提供的精确静态工厂方法以及他们相对应的工具类。

注意:Guava引入的新的集合类型不会暴漏未加工的构造器,也不会在实用工具类中有初始化器。而是他们直接暴漏静态工厂方法,例如:

Multiset<String> multiset = HashMultiset.create();

Iterables

只要可能,Guava更倾向于提供接收Iterable的工具而不是Collection。在Google,遇到实际没有在主内存中存储的"集合"并不是不寻常的,而是从数据库或者从另外一个数据中心收集,没有实际抓取所有元素不能支持像size()操作。

因此,你可以期望看到对所有集合支持的许多操作可以在Iterables中找到。除此之外,大多数Iterables方法在接收原始迭代器的Iterators中有相对应的版本。

Iterables类中难以抗拒的大多数操作时延迟的:他们只有在必要的时候才会推进支持迭代。他们本身返回的Iterables方法返回延迟计算视图,而不是在内存中明确的构造一个集合。

从Guava 12开始,Iterables通过FluentIterable类提供,其封装了Iterable并且对许多这些操作提供”流式“语法。

以下是大多数常用的工具的选择,而且Iterables中许多更加“函数式”方法在Guava函数式语义讨论。

普遍的
方法描述也可查看
concat(Iterable<Iterable>)返回一个几个迭代的串联延迟视图concat(Iterable...)
frequency(Iterable, Object)返回对象连接的个数比较Collections.frequency(Collection, Object);请查看Multiset
partition(Iterable, int)返回可迭代被分区到指定大小的块的一个不可修改的视图Lists.partition(List, int)paddedPartition(Iterable, int)
getFirst(Iterable, T default)返回可迭代的第一个元素,如果为空则返回默认值比较Iterable.iterator().next(),FluentIterable.first()
getLast(Iterable)返回可迭代的最后一个元素,如果为空则使用NoSuchElementException快速失败getLast(Iterable, T default),FluentIterable.last()
getLast(Iterable)返回可迭代的最后一个元素,如果为空则使用NoSuchElementException快速失败getLast(Iterable, T default),FluentIterable.last()
elementsEqual(Iterable, Iterable)如果迭代按照相同的排序有相同的元素则返回true比较List.equals(Object)
unmodifiableIterable(Iterable)返回迭代不可变视图比较Collections.unmodifiableCollection(Collection)
limit(Iterable, int)返回Iterable,其返回指定元素数量的最大值FluentIterable.limit(int)
getOnlyElement(Iterable)返回在Iterable中的唯一元素。如果迭代为空或者有多个元素则快速失败getOnlyElement(Iterable, T default)
Iterable<Integer> concatenated = Iterables.concat(
  Ints.asList(1, 2, 3),
  Ints.asList(4, 5, 6));
// concatenated has elements 1, 2, 3, 4, 5, 6

String lastAdded = Iterables.getLast(myLinkedHashSet);

String theElement = Iterables.getOnlyElement(thisSetIsDefinitelyASingleton);
  // if this set isn't a singleton, something is wrong!
集合风格

通常,集合自然地在其他集合的支持这些操作,而不是在迭代上。

当输入实际是一个Collection时,每一个这些操作都委托给相对应的Collection接口方法。
例如,如果Iterables.size被传入一个Collection,它将调用Collection.size方法而不是遍历迭代器。

方法类似地Collection方法FluentIterable等价的
addAll(Collection addTo, Iterable toAdd)Collection.addAll(Collection)
contains(Iterable, Object)Collection.contains(Object)FluentIterable.contains(Object)
removeAll(Iterable removeFrom, Collection toRemove)Collection.removeAll(Collection)
retainAll(Iterable removeFrom, Collection toRetain)Collection.retainAll(Collection)
size(Iterable)Collection.size()FluentIterable.size()
toArray(Iterable, Class)Collection.toArray(T[])FluentIterable.toArray(Class)
isEmpty(Iterable)Collection.isEmpty()FluentIterable.isEmpty()
get(Iterable, int)List.get(int)FluentIterable.get(int)
toString(Iterable)Collection.toString()FluentIterable.toString()
FluentIterable

除了上面提到的方法和使用函数式习惯[主题]函数,FluentIterable有一些方便方法用于复制到不可变集合:

结果类型方法
ImmutableListtoImmutableList()
ImmutableSettoImmutableSet()
ImmutableSortedSettoImmutableSortedSet(Comparator)
Lists

除了静态的构造器方法和函数式编程方法,Lists提供多个关于List对象的有价值的工具。

方法描述
partition(List, int)返回底层list的视图,切分为指定大小的块
reverse(List)返回指定list的相反视图。注意:如果list是不可变的,考虑使用ImmutableList.reverse()代替
List<Integer> countUp = Ints.asList(1, 2, 3, 4, 5);
List<Integer> countDown = Lists.reverse(theList); // {5, 4, 3, 2, 1}
List<List<Integer>> parts = Lists.partition(countUp, 2); // {{1, 2}, {3, 4}, {5}}
静态工厂方法

Lists提供以下静态工厂方法:

实现工厂
ArrayList基本的,带元素,来自Iterable,有精确容量,有期望大小,来自Iterator
LinkedList基本的,来自Iterable

比较器(Comparators)

查找一些元素的最小或者最小值

一个看似简单的任务(找出一些元素的最小或者最大),由于希望最小化分配,装箱和位于不同位置的API而变得复杂。下表总结了此任务的最佳实践。
下表只展示了max()解决方案,但是相同建议适用于查找min()

你在比较什么恰好2实例超过2实例
未封箱的数字原生类型(即long,int,double,floatMath.max(a, b)Long.max(a, b, c),Ints.max(a, b, c)等等
Comparable实例(即,Duration,String,Long等等)Comparators.max(a, b)Collections.max(asList(a, b, c))
使用自定义的Comparator(即,使用myComparatorMyType)Comparators.max(a, b, cmp)Collections.max(asList(a, b, c), cmp)

注意:我们推荐静态引入在这些解决方案中提到的所有方法来简化你的代码(即max(asList(a, b, c))优先级高于Collections.max(Arrays.asList(a, b, c)))。

Sets

Sets工具类包含多个魔改方法。

Set-Theoretic Operations(Set理论操作)

我们提供多个标准的Set理论操作,在参数集合上作为视图实现。这些返回一个SetView,其可以被用于:

  • 直接当作Set,因为它实现了Set接口。
  • 使用copyInfo(Set)将它复制到一个可变集合。
  • 使用immutableCopy()创建一个不可变复制。
方法
union(Set, Set)
intersection(Set, Set)
difference(Set, Set)
symmetricDifference(Set, Set)

例如:

Set<String> wordsWithPrimeLength = ImmutableSet.of("one", "two", "three", "six", "seven", "eight");
Set<String> primes = ImmutableSet.of("two", "three", "five", "seven");

SetView<String> intersection = Sets.intersection(primes, wordsWithPrimeLength); // contains "two", "three", "seven"
// I can use intersection as a Set directly, but copying it can be more efficient if I use it a lot.
return intersection.immutableCopy();
其他Set工具
方法描述也可以看
cartesianProduct(List<Set>)返回从每一个set的选择一个元素即可获得每一个可能的列表cartesianProduct(Set...)
powerSet(Set)返回指定集合的子集集合
Set<String> animals = ImmutableSet.of("gerbil", "hamster");
Set<String> fruits = ImmutableSet.of("apple", "orange", "banana");

Set<List<String>> product = Sets.cartesianProduct(animals, fruits);
// {{"gerbil", "apple"}, {"gerbil", "orange"}, {"gerbil", "banana"},
//  {"hamster", "apple"}, {"hamster", "orange"}, {"hamster", "banana"}}

Set<Set<String>> animalSets = Sets.powerSet(animals);
// {{}, {"gerbil"}, {"hamster"}, {"gerbil", "hamster"}}
静态工厂

Sets提供以下静态工厂方法:

实现工厂
HashSetbasic,使用元素,Iterable,使用期望大小,Iterator
LinkedHashSetbasic,Iterable,使用期望大小
TreeSetbasic,使用Comparator,Iterable

Maps

Maps有多个非常酷的工具,值得单独说明。

uniqueIndex

Maps.uniqueIndex(Iterable, Function)解决存在一堆对象,这些对象每一个有一些唯一属性,并且可以基于这些属性查找这些对象的常见情况。

假如说我们有一堆字符串,而且我们知道有唯一长度,并且我们想要可以通过特定的长度查找字符串。

ImmutableMap<Integer, String> stringsByIndex = Maps.uniqueIndex(strings, new Function<String, Integer> () {
    public Integer apply(String string) {
      return string.length();
    }
  });

如果索引不是唯一,请查看下面的Multimaps.index

difference

Maps.difference(Map, Map)允许你比较两个map之间的所有不同。它返回MapDifference对象,其Venn图分解为:

方法描述
entriesInCommon()在两个map中的条目,同时匹配key和value
entriesDiffering()相同key的条目,但是不同的值。在此map中的值是MapDifference.ValueDifference类型,其允许你查看左侧和右侧的值
entriesOnlyOnLeft()返回key在左侧没在右侧的条目
entriesOnlyOnRight()返回key在右侧没在左侧的条目
Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);
Map<String, Integer> right = ImmutableMap.of("b", 2, "c", 4, "d", 5);
MapDifference<String, Integer> diff = Maps.difference(left, right);

diff.entriesInCommon(); // {"b" => 2}
diff.entriesDiffering(); // {"c" => (3, 4)}
diff.entriesOnlyOnLeft(); // {"a" => 1}
diff.entriesOnlyOnRight(); // {"d" => 5}
BiMap工具

关于BiMap的Guava工具存在Maps类中,因为BiMap也是一个Map

BiMap工具相对于Map工具
synchronizedBiMap(BiMap)Collections.synchronizedMap(Map)
unmodifiableBiMap(BiMap)Collections.unmodifiableMap(Map)
静态工厂

Maps提供以下静态工厂方法。

实现工厂
HashMap基本的 ,Map,使用期望大小
LinkedHashMap基本的, Map
TreeMap基本的,Comparator,SortedMap
EnumMapClass,Map
ConcurrentMap基本的
IdentityHashMap基本的

Multisets

标准的Collection操作,例如containsAll,忽略在multiset中元素的数量,只关心元素是否在multiset中。Multisets提供多个考虑到元素多样性的操作。

方法说明Collection方法的不同
containsOccurrences(Multiset sup, Multiset sub)对于所有元素o,如果sub.count(o) <= super.count(o)则返回trueCollection.containsAll忽略了数量,仅测试元素是否包含
removeOccurrences(Multiset removeFrom, Multiset toRemove)移除removeFrom中在toRemove元素的出现次数Collection.removeAll移除所有在toRemove中出现的任何元素
retainOccurrences(Multiset removeFrom, Multiset toRetain)对于所有o元素保证removeFrom.count(o) <= toRetain.count(o)Collection.retainAll保留所有在toRetain出现的元素
intersection(Multiset, Multiset)返回两个mltisets的交集视图;一个非破坏性的替代retainOccurrences没有类似功能
Multiset<String> multiset1 = HashMultiset.create();
multiset1.add("a", 2);

Multiset<String> multiset2 = HashMultiset.create();
multiset2.add("a", 5);

multiset1.containsAll(multiset2); // returns true: all unique elements are contained,
  // even though multiset1.count("a") == 2 < multiset2.count("a") == 5
Multisets.containsOccurrences(multiset1, multiset2); // returns false

Multisets.removeOccurrences(multiset2, multiset1); // multiset2 now contains 3 occurrences of "a"

multiset2.removeAll(multiset1); // removes all occurrences of "a" from multiset2, even though multiset1.count("a") == 2
multiset2.isEmpty(); // returns true

Multisets其他工具包含:

方法描述
copyHighestCountFirst(Multiset)返回一个不可变的multiset复制,按照频率降序遍历元素
unmodifiableMultiset(Multiset)返回multiset一个不可变的视图
unmodifiableSortedMultiset(SortedMultiset)返回排序的multiset一个不可变的视图
Multiset<String> multiset = HashMultiset.create();
multiset.add("a", 3);
multiset.add("b", 5);
multiset.add("c", 1);

ImmutableMultiset<String> highestCountFirst = Multisets.copyHighestCountFirst(multiset);

// highestCountFirst, like its entrySet and elementSet, iterates over the elements in order {"b", "a", "c"}

Multimaps

Multimaps提供多个普通的工具操作,值得单独说明。

index

Maps.uniqueIndex类似功能,Multimaps.index(Iterable, Function)解决当你想要可以使用相同的方式查找带有一些特定属性的所有对象时的情况,其不用必须唯一。
一般来说,我们想要基于他们的长度将字符串分组。

ImmutableSet<String> digits = ImmutableSet.of(
    "zero", "one", "two", "three", "four",
    "five", "six", "seven", "eight", "nine");
Function<String, Integer> lengthFunction = new Function<String, Integer>() {
  public Integer apply(String string) {
    return string.length();
  }
};
ImmutableListMultimap<Integer, String> digitsByLength = Multimaps.index(digits, lengthFunction);
/*
 * digitsByLength maps:
 *  3 => {"one", "two", "six"}
 *  4 => {"zero", "four", "five", "nine"}
 *  5 => {"three", "seven", "eight"}
 */
invertFrom

因为Multimap可以将许多key映射到一个值并且一个key映射到许多值,对于转换成一个Multimap非常有用。Guava提供invertFrom(Multimap toInvert, Multimap dest)来允许你做这件事,无需为你选择一个实现。

注意:如果你正在使用ImmutableMultimap,考虑使用ImmutableMultimap.inverse()代替。

ArrayListMultimap<String, Integer> multimap = ArrayListMultimap.create();
multimap.putAll("b", Ints.asList(2, 4, 6));
multimap.putAll("a", Ints.asList(4, 2, 1));
multimap.putAll("c", Ints.asList(2, 5, 3));

TreeMultimap<Integer, String> inverse = Multimaps.invertFrom(multimap, TreeMultimap.<Integer, String>create());
// note that we choose the implementation, so if we use a TreeMultimap, we get results in order
/*
 * inverse maps:
 *  1 => {"a"}
 *  2 => {"a", "b", "c"}
 *  3 => {"c"}
 *  4 => {"a", "b"}
 *  5 => {"c"}
 *  6 => {"b"}
 */
forMap

需要对Map上使用Multimap方法?forMap(Map)Map做为SetMultimap展示。这是相当有用的,例如,与Multimaps.invertFrom结合。

Map<String, Integer> map = ImmutableMap.of("a", 1, "b", 1, "c", 2);
SetMultimap<String, Integer> multimap = Multimaps.forMap(map);
// multimap maps ["a" => {1}, "b" => {1}, "c" => {2}]
Multimap<Integer, String> inverse = Multimaps.invertFrom(multimap, HashMultimap.<Integer, String> create());
// inverse maps [1 => {"a", "b"}, 2 => {"c"}]
封装器

Multimaps提供传统的封装器方法,以及得到基于你选择的MapCollection实现的自定义Multimap实现工具。

Multimap类型不可修改的同步的自定义的
MultimapunmodifiableMultimapsynchronizedMultimapnewMultimap
ListMultimapunmodifiableListMultimapsynchronizedListMultimapnewListMultimap
SetMultimapunmodifiableSetMultimapsynchronizedSetMultimapnewSetMultimap
SortedSetMultimapunmodifiableSortedSetMultimapsynchronizedSortedSetMultimapnewSortedSetMultimap

自定义的Multimap实现允许你指定一个特定实现,其返回的Multimap中使用。说明包括:

  • multimap假设对map和工厂返回的列表拥有完全的所有权。这些对象不应该被手动更新,当提供时他们应该为空,并且他们不应该使用soft,weak或者phontom引用。
  • 在你修改Multimap之后,没有任何保证Map的内容会是什么样子。
  • 当并发操作更新multimap时,multimap不是线程安全的,尽管map和实例通过工厂生成。不过,并发读将会正确地工作。必要时使用synchronized封装器解决这个问题。
  • 如果map,工厂,列表通过工厂生成multimap是可序列化的,并且multimap内容所有都是可序列化的。
  • Multimap.get(key)返回的集合与你的Supplier返回的集合不是相同的类型,尽管如果你的supplier返回RandomAccess列表,通过Multimap.get(key)返回的列表也将被随机访问。

注意:自定义Multimap方法期望一个Supplier参数来生成新的集合。这有一个编写ListMultimap的示例,它由TreeMap映射到LinkedList

ListMultimap<String, Integer> myMultimap = Multimaps.newListMultimap(
  Maps.<String, Collection<Integer>>newTreeMap(),
  new Supplier<LinkedList<Integer>>() {
    public LinkedList<Integer> get() {
      return Lists.newLinkedList();
    }
  });

Tables

Tables类提供几个好用的工具。

customTable

相比Multimaps.newXXXMultimap(Map, Supplier)工具,Tables.newCustomTable(Map, Supplier<Map>)允许你指定一个你喜欢的 无论什么行或者列map的Table实现。

// use LinkedHashMaps instead of HashMaps
Table<String, Character, Integer> table = Tables.newCustomTable(
  Maps.<String, Map<Character, Integer>>newLinkedHashMap(),
  new Supplier<Map<Character, Integer>> () {
    public Map<Character, Integer> get() {
      return Maps.newLinkedHashMap();
    }
  });
transpose

transpose(Table<R, C, V>)方法允许你将Table<R, C, V>作为Table<C, R, V>展示。

封装器

这些都是你熟悉和喜爱的不可修改的封装器。但是,在大多情况下使用ImmutableTable

Collection助手

介绍

有时你需要编写自己的集合扩展。当元素添加到一个列表时,可能你想要添加特殊行为,或者你想要编写一个实际是数据库查询支持的Iterable。Guava提供多个工具来使这些任务更容易。(毕竟,我们现在在做的使扩展集合框架工作)

Forwarding适配器

对于所有各种各样的集合接口,Guava提供Forwarding抽象类来简化使用适配器模式

Forwarding类定义一个抽象方法,delegate(),你应该重写返回适配器对象。每一个其他方法直接委托到委托对象:例如,ForwardingList.get(int)简单的实现为delegate().get(int)

通过子类ForwardingXXX并实现delegate()方法,你可以仅重写在目标类被选择的方法,添加适配功能,没有必要代理你自己的每一个方法。

除此之外,许多方法有一个standardMethod实现,你可以用来恢复预期的行为,提供一些与扩展AbstractList或者其他JDK中基本类相同的好处。

让我们来做一个示例。假设你想要适配List以便它记录所有元素添加到它。当然,我们想要记录元素,跟哪个方法被使用来添加他们无关 – add(int, E),add(E),addAll(Collection) --所以我们必须重写所有这些方法。

class AddLoggingList<E> extends ForwardingList<E> {
  final List<E> delegate; // backing list
  @Override protected List<E> delegate() {
    return delegate;
  }
  @Override public void add(int index, E elem) {
    log(index, elem);
    super.add(index, elem);
  }
  @Override public boolean add(E elem) {
    return standardAdd(elem); // implements in terms of add(int, E)
  }
  @Override public boolean addAll(Collection<? extends E> c) {
    return standardAddAll(c); // implements in terms of add
  }
}

记住,默认情况下,所有方法直接转向代理,所以重写ForwardingMap.put将不会修改ForwardingMap.putAll的行为。注意重载每一个方法,他的行为肯定被修改了,并确保你的适配集合满足它的约定。

通常,抽象集合基础能力提供的大多数方法,就像AbstractList,也可以作为Forwarding适配器中standard实现被提供。

提供特殊视图的接口有时提供这些视图的Standard实现。例如,ForwardingMap提供StandardKeySet,StandardValuesStandardEntrySet类,每个类尽可能地委托他们的方法到适配器map,否则,他们将不能委托的方法保留为抽象方法。

接口Forwarding适配器
CollectionForwardingCollection
ListForwardingList
SetForwardingSet
SortedSetForwardingSortedSet
MapForwardingMap
SortedMapForwardingSortedMap
ConcurrentMapForwardingConcurrentMap
Map.EntryForwardingMapEntry
QueueForwardingQueue
IteratorForwardingIterator
ListIteratorForwardingListIterator
MultisetForwardingMultiset
MultimapForwardingMultimap
ListMultimapForwardingListMultimap
SetMultimapForwardingSetMultimap

PeekingIterator

有时普通的Interator接口并不满足。

Interators支持方法Iterators.peekingIterator(Iterator),它封装了Iterator并返回PeekingIterator,Iterator子类型,允许你peek()在下一次调用next()时返回的元素。

注意:通过Iterators.peekingIterator返回的PeekingIteratorpeek()之后不支持remove()调用。

我们来做一个示例:复制List同时消除连续重复的元素。

List<E> result = Lists.newArrayList();
PeekingIterator<E> iter = Iterators.peekingIterator(source.iterator());
while (iter.hasNext()) {
  E current = iter.next();
  while (iter.hasNext() && iter.peek().equals(current)) {
    // skip this duplicate element
    iter.next();
  }
  result.add(current);
}

传统的方式来这样做涉及保持前一个元素的踪迹,然后在某一个条件下返回,但是这比较难以应付并且易bug的业务。PeekingIterator是比较简单理解和使用的。

AbstractIterator

实现你自己的Iterator?AbstractIterator可以让你的生活更轻松。

使用一个示例更容易说明。也就是说我们想要封装一个迭代器,正如来忽略null值。

public static Iterator<String> skipNulls(final Iterator<String> in) {
  return new AbstractIterator<String>() {
    protected String computeNext() {
      while (in.hasNext()) {
        String s = in.next();
        if (s != null) {
          return s;
        }
      }
      return endOfData();
    }
  };
}

你实现了一个方法,computeNext(),只是计算下一个值。当序列完成,只是返回endOfData()来标记迭代结束。

注意:AbstractIterator继承UnmodifiableIterator,其禁止remove()实现。如果你需要一个支持remove()迭代,你应该不继承AbstractIterator

AbstractSequentialIterator

一些迭代器更容易使用其他方法表示。AbstractSequentialIterator提供其他方法的表示一个迭代。

Iterator<Integer> powersOfTwo = new AbstractSequentialIterator<Integer>(1) { // note the initial value!
  protected Integer computeNext(Integer previous) {
    return (previous == 1 << 30) ? null : previous * 2;
  }
};

这个,我们实现方法computeNext(T),其接受前面的值作为参数。

注意:你必须额外传入一个初始值,而且如果迭代立即终止返回null。注意computeNext假设null值意味着迭代的结束 – AbstractSequentialIterator不能用于实现一个可能返回null迭代器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值