本部分为guava笔记第二部分,主要整理guava中的集合类——不可变集合、新集合类型、集合工具类。
不可变集合
范例
1 | public static final ImmutableSet<String> COLOR_NAMES = ImmutableSet.of( |
为什么使用不可变集合
不可变对象有很多优点,包括:
- 当对象被不可信的库调用时,不可变形式是安全的;
- 不可变对象被多个线程调用时,不存在竞态条件问题
- 不可变集合不需要考虑变化,因此可以节省时间和空间。所有不可变的集合都比它们的可变形式有更好的内存利用率(分析和测试细节);
- 不可变对象因为有固定不变,可以作为常量来安全使用。
创建对象的不可变拷贝是一项很好的防御性编程技巧。Guava为所有JDK标准集合类型和Guava新集合类型都提供了简单易用的不可变版本。
JDK也提供了Collections.unmodifiableXXX方法把集合包装为不可变形式,但我们认为不够好:
- 笨重而且累赘:不能舒适地用在所有想做防御性拷贝的场景;
- 不安全:要保证没人通过原集合的引用进行修改,返回的集合才是事实上不可变的;
- 低效:包装过的集合仍然保有可变集合的开销,比如并发修改的检查、散列表的额外空间,等等。
如果你没有修改某个集合的需求,或者希望某个集合保持不变时,把它防御性地拷贝到不可变集合是个很好的实践。
重要提示:所有Guava不可变集合的实现都不接受null值。我们对Google内部的代码库做过详细研究,发现只有5%的情况需要在集合中允许null元素,剩下的95%场景都是遇到null值就快速失败。如果你需要在不可变集合中使用null,请使用JDK中的Collections.unmodifiableXXX方法。
怎么使用不可变集合
不可变集合可以用如下多种方式创建:
- copyOf方法,如ImmutableSet.copyOf(set);
- of方法,如ImmutableSet.of(“a”, “b”, “c”)或 ImmutableMap.of(“a”, 1, “b”, 2);
- Builder工具,如
1 | public static final ImmutableSet<Color> GOOGLE_COLORS = |
此外,对有序不可变集合来说,排序是在构造集合的时候完成的,如:
1 | ImmutableSortedSet.of("a", "b", "c", "a", "d", "b"); |
会在构造时就把元素排序为a, b, c, d。
比想象中更智能的copyOf
请注意,ImmutableXXX.copyOf方法会尝试在安全的时候避免做拷贝——实际的实现细节不详,但通常来说是很智能的,比如:
1 | ImmutableSet<String> foobar = ImmutableSet.of("foo", "bar", "baz"); |
在这段代码中,ImmutableList.copyOf(foobar)会智能地直接返回foobar.asList(),它是一个ImmutableSet的常量时间复杂度的List视图。
作为一种探索,ImmutableXXX.copyOf(ImmutableCollection)会试图对如下情况避免线性时间拷贝:
- 在常量时间内使用底层数据结构是可能的——例如,ImmutableSet.copyOf(ImmutableList)就不能在常量时间内完成。
- 不会造成内存泄露——例如,你有个很大的不可变集合ImmutableList
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方法。
关联可变集合和不可变集合
可变集合接口 | 属于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 |
新集合类型
Guava引入了很多JDK没有的、但我们发现明显有用的新集合类型。这些新类型是为了和JDK集合框架共存,而没有往JDK集合抽象中硬塞其他概念。作为一般规则,Guava集合非常精准地遵循了JDK接口契约。
Multiset
统计一个词在文档中出现了多少次,传统的做法是这样的:
1 | Map<String, Integer> counts = new HashMap<String, Integer>(); |
这种写法很笨拙,也容易出错,并且不支持同时收集多种统计信息,如总词数。我们可以做的更好。
Guava提供了一个新集合类型 Multiset,它可以多次添加相等的元素。维基百科从数学角度这样定义Multiset:”集合[set]概念的延伸,它的元素可以重复出现…与集合[set]相同而与元组[tuple]相反的是,Multiset元素的顺序是无关紧要的:Multiset {a, a, b}和{a, b, a}是相等的”。——译者注:这里所说的集合[set]是数学上的概念,Multiset继承自JDK中的Collection接口,而不是Set接口,所以包含重复元素并没有违反原有的接口契约。
可以用两种方式看待Multiset:
- 没有元素顺序限制的
ArrayList<E>
Map<E, Integer>
,键为元素,值为计数
Guava的Multiset API也结合考虑了这两种方式:
当把Multiset看成普通的Collection时,它表现得就像无序的ArrayList:
- add(E)添加单个给定元素
- iterator()返回一个迭代器,包含Multiset的所有元素(包括重复的元素)
- size()返回所有元素的总个数(包括重复的元素)
当把Multiset看作Map<E, Integer>
时,它也提供了符合性能期望的查询操作:
- count(Object)返回给定元素的计数。HashMultiset.count的复杂度为O(1),TreeMultiset.count的复杂度为O(log n)。
- entrySet()返回
Set<Multiset.Entry<E>>
,和Map的entrySet类似。 - elementSet()返回所有不重复元素的
Set<E>
,和Map的keySet()类似。 - 所有Multiset实现的内存消耗随着不重复元素的个数线性增长。
值得注意的是,除了极少数情况,Multiset和JDK中原有的Collection接口契约完全一致——具体来说,TreeMultiset在判断元素是否相等时,与TreeSet一样用compare,而不是Object.equals。另外特别注意,Multiset.addAll(Collection)可以添加Collection中的所有元素并进行计数,这比用for循环往Map添加元素和计数方便多了。
方法 | 描述 |
---|---|
count(E) | 给定元素在Multiset中的计数 |
elementSet() | Multiset中不重复元素的集合,类型为Set<E> |
entrySet() | 和Map的entrySet类似,返回Set<Multiset.Entry<E>> ,其中包含的Entry支持getElement()和getCount()方法 |
add(E, int) | 增加给定元素在Multiset中的计数 |
[remove(E, int)](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multiset.html#remove(java.lang.Object, int)) | 减少给定元素在Multiset中的计数 |
[setCount(E, int)](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multiset.html#setCount(E, int)) | 设置给定元素在Multiset中的计数,不可以为负数 |
size() | 返回集合元素的总个数(包括重复的元素) |
Multiset不是Map
请注意,Multiset<E
>不是Map<E, Integer>
,虽然Map可能是某些Multiset实现的一部分。准确来说Multiset是一种Collection类型,并履行了Collection接口相关的契约。关于Multiset和Map的显著区别还包括:
- Multiset中的元素计数只能是正数。任何元素的计数都不能为负,也不能是0。elementSet()和entrySet()视图中也不会有这样的元素。
- multiset.size()返回集合的大小,等同于所有元素计数的总和。对于不重复元素的个数,应使用elementSet().size()方法。(因此,add(E)把multiset.size()增加1)
- multiset.iterator()会迭代重复元素,因此迭代长度等于multiset.size()。
- Multiset支持直接增加、减少或设置元素的计数。setCount(elem, 0)等同于移除所有elem。
- 对multiset 中没有的元素,multiset.count(elem)始终返回0。
Multiset的各种实现
Guava提供了多种Multiset的实现,大致对应JDK中Map的各种实现:
Map | 对应的Multiset | 是否支持null元素 |
---|---|---|
HashMap | HashMultiset | 是 |
TreeMap | TreeMultiset | 是(如果comparator支持的话) |
LinkedHashMap | LinkedHashMultiset | 是 |
ConcurrentHashMap | ConcurrentHashMultiset | 否 |
ImmutableMap | ImmutableMultiset | 否 |
SortedMultiset
SortedMultiset是Multiset 接口的变种,它支持高效地获取指定范围的子集。比方说,你可以用 latencies.subMultiset(0,BoundType.CLOSED, 100, BoundType.OPEN).size()来统计你的站点中延迟在100毫秒以内的访问,然后把这个值和latencies.size()相比,以获取这个延迟水平在总体访问中的比例。
TreeMultiset实现SortedMultiset接口。在撰写本文档时,ImmutableSortedMultiset还在测试和GWT的兼容性。
Multimap
每个有经验的Java程序员都在某处实现过Map<K, List<V>>
或Map<K, Set<V>>
,并且要忍受这个结构的笨拙。例如,Map<K, Set<V>>
通常用来表示非标定有向图。Guava的 Multimap可以很容易地把一个键映射到多个值。换句话说,Multimap是把键映射到任意多个值的一般方式。
可以用两种方式思考Multimap的概念:”键-单个值映射”的集合:
a -> 1 a -> 2 a ->4 b -> 3 c -> 5
或者”键-值集合映射”的映射:
a -> [1, 2, 4] b -> 3 c -> 5
一般来说,Multimap接口应该用第一种方式看待,但asMap()视图返回Map<K, Collection<V>>
,让你可以按另一种方式看待Multimap。重要的是,不会有任何键映射到空集合:一个键要么至少到一个值,要么根本就不在Multimap中。
很少会直接使用Multimap接口,更多时候你会用ListMultimap或SetMultimap接口,它们分别把键映射到List或Set。
修改Multimap
Multimap.get(key)以集合形式返回键所对应的值视图,即使没有任何对应的值,也会返回空集合。ListMultimap.get(key)返回List,SetMultimap.get(key)返回Set。
对值视图集合进行的修改最终都会反映到底层的Multimap。例如:
1 | Set<Person> aliceChildren = childrenMultimap.get(alice); |
其他(更直接地)修改Multimap的方法有:
方法签名 | 描述 | 等价于 |
---|---|---|
[put(K, V)](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multimap.html#put(K, V)) | 添加键到单个值的映射 | multimap.get(key).add(value) |
[putAll(K, Iterable)](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multimap.html#putAll(K, java.lang.Iterable)) | 依次添加键到多个值的映射 | Iterables.addAll(multimap.get(key), values) |
[remove(K, V)](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multimap.html#remove(java.lang.Object, java.lang.Object)) | 移除键到值的映射;如果有这样的键值并成功移除,返回true。 | multimap.get(key).remove(value) |
removeAll(K) | 清除键对应的所有值,返回的集合包含所有之前映射到K的值,但修改这个集合就不会影响Multimap了。 | multimap.get(key).clear() |
[replaceValues(K, Iterable)](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multimap.html#replaceValues(K, java.lang.Iterable)) | 清除键对应的所有值,并重新把key关联到Iterable中的每个元素。返回的集合包含所有之前映射到K的值。 | multimap.get(key).clear(); Iterables.addAll(multimap.get(key), values) |
Multimap的视图
Multimap还支持若干强大的视图:
asMap
为Multimap<K, V>
提供Map<K,Collection<V>>
形式的视图。返回的Map支持remove操作,并且会反映到底层的Multimap,但它不支持put或putAll操作。更重要的是,如果你想为Multimap中没有的键返回null,而不是一个新的、可写的空集合,你就可以使用asMap().get(key)。(你可以并且应当把asMap.get(key)返回的结果转化为适当的集合类型——如SetMultimap.asMap.get(key)的结果转为Set,ListMultimap.asMap.get(key)的结果转为List——Java类型系统不允许ListMultimap直接为asMap.get(key)返回List——译者注:也可以用Multimaps中的asMap静态方法帮你完成类型转换)entries
用Collection<Map.Entry<K, V>>
返回Multimap中所有”键-单个值映射”——包括重复键。(对SetMultimap,返回的是Set)keySet
用Set表示Multimap中所有不同的键。keys
用Multiset表示Multimap中的所有键,每个键重复出现的次数等于它映射的值的个数。可以从这个Multiset中移除元素,但不能做添加操作;移除操作会反映到底层的Multimap。values()
用一个”扁平”的Collection<V>
包含Multimap中的所有值。这有一点类似于Iterables.concat(multimap.asMap().values()),但它直接返回了单个Collection,而不像multimap.asMap().values()那样是按键区分开的Collection。
Multimap不是Map
Multimap<K, V>
不是Map<K,Collection<V>>
,虽然某些Multimap实现中可能使用了map。它们之间的显著区别包括:
- Multimap.get(key)总是返回非null、但是可能空的集合。这并不意味着Multimap为相应的键花费内存创建了集合,而只是提供一个集合视图方便你为键增加映射值——译者注:如果有这样的键,返回的集合只是包装了**Multimap中已有的集合;如果没有这样的键,返回的空集合也只是持有Multimap引用的栈对象,让你可以用来操作底层的Multimap。因此,返回的集合不会占据太多内存,数据实际上还是存放在Multimap中。
- 如果你更喜欢像Map那样,为Multimap中没有的键返回null,请使用asMap()视图获取一个
Map<K, Collection<V>>
。(或者用静态方法Multimaps.asMap()为ListMultimap返回一个Map<K, List<V>>
。对于SetMultimap和SortedSetMultimap,也有类似的静态方法存在) - 当且仅当有值映射到键时,Multimap.containsKey(key)才会返回true。尤其需要注意的是,如果键k之前映射过一个或多个值,但它们都被移除后,Multimap.containsKey(key)会返回false。
- Multimap.entries()返回Multimap中所有”键-单个值映射”——包括重复键。如果你想要得到所有”键-值集合映射”,请使用asMap().entrySet()。
- Multimap.size()返回所有”键-单个值映射”的个数,而非不同键的个数。要得到不同键的个数,请改用Multimap.keySet().size()。
Multimap的各种实现
Multimap提供了多种形式的实现。在大多数要使用Map<K, Collection<V>>
的地方,你都可以使用它们:
实现 | 键行为类似 | 值行为类似 |
---|---|---|
ArrayListMultimap | HashMap | ArrayList |
HashMultimap | HashMap | HashSet |
LinkedListMultimap | LinkedHashMap | LinkedList |
LinkedHashMultimap | LinkedHashMap | LinkedHashMap |
TreeMultimap | TreeMap | TreeSet |
ImmutableListMultimap |
ImmutableMap | ImmutableList |
ImmutableSetMultimap | ImmutableMap | ImmutableSet |
除了两个不可变形式的实现,其他所有实现都支持null键和null值
LinkedListMultimap.entries()保留了所有键和值的迭代顺序。详情见doc链接。
LinkedHashMultimap保留了映射项的插入顺序,包括键插入的顺序,以及键映射的所有值的插入顺序。
请注意,并非所有的Multimap都和上面列出的一样,使用Map<K, Collection<V>>
来实现(特别是,一些Multimap实现用了自定义的hashTable,以最小化开销)
如果你想要更大的定制化,请用Multimaps.newMultimap(Map, Supplier)或[list](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/Multimaps.html#newListMultimap(java.util.Map, com.google.common.base.Supplier))和 [set](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/Multimaps.html#newSetMultimap(java.util.Map, com.google.common.base.Supplier))版本,使用自定义的Collection、List或Set实现Multimap。
BiMap
传统上,实现键值对的双向映射需要维护两个单独的map,并保持它们间的同步。但这种方式很容易出错,而且对于值已经在map中的情况,会变得非常混乱。例如:
1 | Map<String, Integer> nameToId = Maps.newHashMap(); |
BiMap是特殊的Map:
在BiMap中,如果你想把键映射到已经存在的值,会抛出IllegalArgumentException异常。如果对特定值,你想要强制替换它的键,请使用 BiMap.forcePut(key, value)。
1 | BiMap<String, Integer> userId = HashBiMap.create(); |
BiMap的各种实现
键–值实现 | 值–键实现 | 对应的BiMap实现 |
---|---|---|
HashMap | HashMap | HashBiMap |
ImmutableMap | ImmutableMap | ImmutableBiMap |
EnumMap | EnumMap | EnumBiMap |
EnumMap | HashMap | EnumHashBiMap |
注:Maps类中还有一些诸如synchronizedBiMap的BiMap工具方法.
Table
1 | Table<Vertex, Vertex, Double> weightedGraph = HashBasedTable.create(); |
通常来说,当你想使用多个键做索引的时候,你可能会用类似Map<FirstName, Map<LastName, Person>>
的实现,这种方式很丑陋,使用上也不友好。Guava为此提供了新集合类型Table,它有两个支持所有类型的键:”行”和”列”。Table提供多种视图,以便你从各种角度使用它:
- rowMap():用
Map<R, Map<C, V>>
表现Table<R, C, V>
。同样的, rowKeySet()返回”行”的集合Set<R>
。 - row(r) :用
Map<C, V>
返回给定”行”的所有列,对这个map进行的写操作也将写入Table中。 - 类似的列访问方法:columnMap()、columnKeySet()、column(c)。(基于列的访问会比基于的行访问稍微低效点)
- cellSet():用元素类型为Table.Cell的Set表现
Table<R, C, V>
。Cell类似于Map.Entry,但它是用行和列两个键区分的。
Table有如下几种实现:
- HashBasedTable:本质上用
HashMap<R, HashMap<C, V>>
实现; - TreeBasedTable:本质上用
TreeMap<R, TreeMap<C,V>>
实现; - ImmutableTable:本质上用
ImmutableMap<R, ImmutableMap<C, V>>
实现;注:ImmutableTable对稀疏或密集的数据集都有优化。 - ArrayTable:要求在构造时就指定行和列的大小,本质上由一个二维数组实现,以提升访问速度和密集Table的内存利用率。ArrayTable与其他Table的工作原理有点不同,请参见Javadoc了解详情。
ClassToInstanceMap
ClassToInstanceMap是一种特殊的Map:它的键是类型,而值是符合键所指类型的对象。
为了扩展Map接口,ClassToInstanceMap额外声明了两个方法:T getInstance(Class) 和T putInstance(Class, T),从而避免强制类型转换,同时保证了类型安全。
ClassToInstanceMap有唯一的泛型参数,通常称为B,代表Map支持的所有类型的上界。例如:
1 | ClassToInstanceMap<Number> numberDefaults=MutableClassToInstanceMap.create(); |
从技术上讲,ClassToInstanceMap<B>
实现了Map<Class<? extends B>, B>
——或者换句话说,是一个映射B的子类型到对应实例的Map。这让ClassToInstanceMap包含的泛型声明有点令人困惑,但请记住B始终是Map所支持类型的上界——通常B就是Object。
对于ClassToInstanceMap,Guava提供了两种有用的实现:MutableClassToInstanceMap和 ImmutableClassToInstanceMap。
RangeSet
RangeSet描述了一组不相连的、非空的区间。当把一个区间添加到可变的RangeSet时,所有相连的区间会被合并,空区间会被忽略。例如:
1 | RangeSet<Integer> rangeSet = TreeRangeSet.create(); |
请注意,要合并Range.closed(1, 10)和Range.closedOpen(11, 15)这样的区间,你需要首先用Range.canonical(DiscreteDomain)对区间进行预处理,例如DiscreteDomain.integers()。
注:RangeSet不支持GWT,也不支持JDK5和更早版本;因为,RangeSet需要充分利用JDK6中NavigableMap的特性。
RangeSet的视图
RangeSet的实现支持非常广泛的视图:
complement()
:返回RangeSet的补集视图。complement也是RangeSet类型,包含了不相连的、非空的区间。subRangeSet(Range<C>)
:返回RangeSet与给定Range的交集视图。这扩展了传统排序集合中的headSet、subSet和tailSet操作。asRanges()
:用Set<Range<C>>
表现RangeSet,这样可以遍历其中的Range。asSet(DiscreteDomain<C>)
(仅ImmutableRangeSet支持):用ImmutableSortedSet<C>
表现RangeSet,以区间中所有元素的形式而不是区间本身的形式查看。(这个操作不支持DiscreteDomain 和RangeSet都没有上边界,或都没有下边界的情况)
RangeSet的查询方法
为了方便操作,RangeSet直接提供了若干查询方法,其中最突出的有:
contains(C)
:RangeSet最基本的操作,判断RangeSet中是否有任何区间包含给定元素。rangeContaining(C)
:返回包含给定元素的区间;若没有这样的区间,则返回null。encloses(Range<C>)
:简单明了,判断RangeSet中是否有任何区间包括给定区间。span()
:返回包括RangeSet中所有区间的最小区间。
RangeMap
RangeMap描述了”不相交的、非空的区间”到特定值的映射。和RangeSet不同,RangeMap不会合并相邻的映射,即便相邻的区间映射到相同的值。例如:
1 | RangeMap<Integer, String> rangeMap = TreeRangeMap.create(); |
RangeMap的视图
RangeMap提供两个视图:
asMapOfRanges()
:用Map<Range<K>, V>
表现RangeMap。这可以用来遍历RangeMap。subRangeMap(Range<K>)
:用RangeMap类型返回RangeMap与给定Range的交集视图。这扩展了传统的headMap、subMap和tailMap操作。
集合工具类
任何对JDK集合框架有经验的程序员都熟悉和喜欢java.util.Collections
包含的工具方法。Guava沿着这些路线提供了更多的工具方法:适用于所有集合的静态方法。这是Guava最流行和成熟的部分之一。
我们用相对直观的方式把工具类与特定集合接口的对应关系归纳如下:
集合接口 | 属于JDK还是Guava | 对应的Guava工具类 |
---|---|---|
Collection | JDK | Collections2 :不要和java.util.Collections混淆 |
List | JDK | Lists |
Set | JDK | Sets |
SortedSet | JDK | Sets |
Map | JDK | Maps |
SortedMap | JDK | Maps |
Queue | JDK | Queues |
Multiset | Guava | Multisets |
Multimap | Guava | Multimaps |
BiMap | Guava | Maps |
Table | Guava | Tables |
在找类似转化、过滤的方法?请看第四章,函数式风格。
静态工厂方法
在JDK 7之前,构造新的范型集合时要讨厌地重复声明范型:
1 | List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<TypeThatsTooLongForItsOwnGood>(); |
我想我们都认为这很讨厌。因此Guava提供了能够推断范型的静态工厂方法:
1 | List<TypeThatsTooLongForItsOwnGood> list = Lists.newArrayList(); |
可以肯定的是,JDK7版本的钻石操作符(<>)没有这样的麻烦:
1 | List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<>(); |
但Guava的静态工厂方法远不止这么简单。用工厂方法模式,我们可以方便地在初始化时就指定起始元素。
1 | Set<Type> copySet = Sets.newHashSet(elements); |
此外,通过为工厂方法命名(Effective Java第一条),我们可以提高集合初始化大小的可读性:
1 | List<Type> exactly100 = Lists.newArrayListWithCapacity(100); |
确切的静态工厂方法和相应的工具类一起罗列在下面的章节。
注意:Guava引入的新集合类型没有暴露原始构造器,也没有在工具类中提供初始化方法。而是直接在集合类中提供了静态工厂方法,例如:
1 | Multiset<String> multiset = HashMultiset.create(); |
Iterables
在可能的情况下,Guava提供的工具方法更偏向于接受Iterable而不是Collection类型。在Google,对于不存放在主存的集合——比如从数据库或其他数据中心收集的结果集,因为实际上还没有攫取全部数据,这类结果集都不能支持类似size()的操作 ——通常都不会用Collection类型来表示。
因此,很多你期望的支持所有集合的操作都在Iterables
类中。大多数Iterables方法有一个在Iterators类中的对应版本,用来处理Iterator。
截至Guava 1.2版本,Iterables使用FluentIterable类
进行了补充,它包装了一个Iterable实例,并对许多操作提供了”fluent”(链式调用)语法。
下面列出了一些最常用的工具方法,但更多Iterables的函数式方法将在第四章讨论。
常规方法
*译者注:懒视图意味着如果还没访问到某个iterable中的元素,则不会对它进行串联操作。
1 | Iterable<Integer> concatenated = Iterables.concat( |
与Collection方法相似的工具方法
通常来说,Collection的实现天然支持操作其他Collection,但却不能操作Iterable。
下面的方法中,如果传入的Iterable是一个Collection实例,则实际操作将会委托给相应的Collection接口方法。例如,往Iterables.size方法传入是一个Collection实例,它不会真的遍历iterator获取大小,而是直接调用Collection.size。
FluentIterable
除了上面和第四章提到的方法,FluentIterable还有一些便利方法用来把自己拷贝到不可变集合
ImmutableList | |
---|---|
ImmutableSet | toImmutableSet() |
ImmutableSortedSet | toImmutableSortedSet(Comparator) |
Lists
除了静态工厂方法和函数式编程方法,Lists
为List类型的对象提供了若干工具方法。
方法 | 描述 |
---|---|
[partition(List, int) ](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Lists.html#partition(java.util.List, int)) |
把List按指定大小分割 |
reverse(List) |
返回给定List的反转视图。注: 如果List是不可变的,考虑改用ImmutableList.reverse() 。 |
1 | List countUp = Ints.asList(1, 2, 3, 4, 5); |
静态工厂方法
Lists提供如下静态工厂方法:
具体实现类型 | 工厂方法 |
---|---|
ArrayList | basic, with elements, from Iterable , with exact capacity, with expected size, from Iterator |
LinkedList | basic, from Iterable |
Sets
Sets
工具类包含了若干好用的方法。
集合理论方法
我们提供了很多标准的集合运算(Set-Theoretic)方法,这些方法接受Set参数并返回SetView
,可用于:
- 直接当作Set使用,因为SetView也实现了Set接口;
- 用
copyInto(Set)
拷贝进另一个可变集合; - 用
immutableCopy()
对自己做不可变拷贝。
方法 |
---|
[union(Set, Set) ](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Sets.html#union(java.util.Set, java.util.Set)) |
[intersection(Set, Set) ](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Sets.html#intersection(java.util.Set, java.util.Set)) |
[difference(Set, Set) ](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Sets.html#difference(java.util.Set, java.util.Set)) |
[symmetricDifference(Set, Set) ](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Sets.html#symmetricDifference(java.util.Set, java.util.Set)) |
使用范例:
1 | Set<String> wordsWithPrimeLength = ImmutableSet.of("one", "two", "three", "six", "seven", "eight"); |
其他Set工具方法
方法 | 描述 | 另请参见 |
---|---|---|
cartesianProduct(List) |
返回所有集合的笛卡儿积 | cartesianProduct(Set...) |
powerSet(Set) |
返回给定集合的所有子集 |
1 | Set<String> animals = ImmutableSet.of("gerbil", "hamster"); |
静态工厂方法
Sets提供如下静态工厂方法:
具体实现类型 | 工厂方法 |
---|---|
HashSet | basic, with elements, from Iterable , with expected size, from Iterator |
LinkedHashSet | basic, from Iterable , with expected size |
TreeSet | basic, with Comparator , from Iterable |
Maps
Maps
类有若干值得单独说明的、很酷的方法。
uniqueIndex
[Maps.uniqueIndex(Iterable,Function)
](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Maps.html#uniqueIndex(java.lang.Iterable, com.google.common.base.Function))通常针对的场景是:有一组对象,它们在某个属性上分别有独一无二的值,而我们希望能够按照这个属性值查找对象——译者注:这个方法返回一个Map,键为Function返回的属性值,值为Iterable中相应的元素,因此我们可以反复用这个Map进行查找操作。
比方说,我们有一堆字符串,这些字符串的长度都是独一无二的,而我们希望能够按照特定长度查找字符串:
1 | ImmutableMap<Integer, String> stringsByIndex = Maps.uniqueIndex(strings, |
如果索引值不是独一无二的,请参见下面的Multimaps.index方法。
difference
[Maps.difference(Map, Map)
](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Maps.html#difference(java.util.Map, java.util.Map))用来比较两个Map以获取所有不同点。该方法返回MapDifference对象,把不同点的维恩图分解为:
entriesInCommon() |
两个Map中都有的映射项,包括匹配的键与值 |
---|---|
entriesDiffering() |
键相同但是值不同值映射项。返回的Map的值类型为MapDifference.ValueDifference ,以表示左右两个不同的值 |
entriesOnlyOnLeft() |
键只存在于左边Map的映射项 |
entriesOnlyOnRight() |
键只存在于右边Map的映射项 |
处理BiMap的工具方法
Guava中处理BiMap的工具方法在Maps类中,因为BiMap也是一种Map实现。
BiMap工具方法 | 相应的Map工具方法 |
---|---|
synchronizedBiMap(BiMap) |
Collections.synchronizedMap(Map) |
unmodifiableBiMap(BiMap) |
Collections.unmodifiableMap(Map) |
静态工厂方法
Maps提供如下静态工厂方法:
具体实现类型 | 工厂方法 |
---|---|
HashMap | basic, from Map , with expected size |
LinkedHashMap | basic, from Map |
TreeMap | basic, from Comparator , from SortedMap |
EnumMap | from Class , from Map |
ConcurrentMap:支持所有操作 | basic |
IdentityHashMap | basic |
Multisets
标准的Collection操作会忽略Multiset重复元素的个数,而只关心元素是否存在于Multiset中,如containsAll方法。为此,Multisets
提供了若干方法,以顾及Multiset元素的重复性:
方法 | 说明 | 和Collection方法的区别 |
---|---|---|
[containsOccurrences(Multiset sup, Multiset sub) ](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multisets.html#containsOccurrences(com.google.common.collect.Multiset, com.google.common.collect.Multiset)) |
对任意o,如果sub.count(o)<=super.count(o),返回true | Collection.containsAll忽略个数,而只关心sub的元素是否都在super中 |
[removeOccurrences(Multiset removeFrom, Multiset toRemove) ](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multisets.html#removeOccurrences(com.google.common.collect.Multiset, com.google.common.collect.Multiset)) |
对toRemove中的重复元素,仅在removeFrom中删除相同个数。 | Collection.removeAll移除所有出现在toRemove的元素 |
[retainOccurrences(Multiset removeFrom, Multiset toRetain) ](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multisets.html#retainOccurrences(com.google.common.collect.Multiset, com.google.common.collect.Multiset)) |
修改removeFrom,以保证任意o都符合removeFrom.count(o)<=toRetain.count(o) | Collection.retainAll保留所有出现在toRetain的元素 |
[intersection(Multiset, Multiset) ](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multisets.html#intersection(com.google.common.collect.Multiset, com.google.common.collect.Multiset)) |
返回两个multiset的交集; | 没有类似方法 |
1 | Multiset<String> multiset1 = HashMultiset.create(); |
Multisets中的其他工具方法还包括:
copyHighestCountFirst(Multiset) |
返回Multiset的不可变拷贝,并将元素按重复出现的次数做降序排列 |
---|---|
unmodifiableMultiset(Multiset) |
返回Multiset的只读视图 |
unmodifiableSortedMultiset(SortedMultiset) |
返回SortedMultiset的只读视图 |
1 | Multiset<String> multiset = HashMultiset.create(); |
Multimaps
Multimaps
提供了若干值得单独说明的通用工具方法
index
作为Maps.uniqueIndex的兄弟方法,[Multimaps.index(Iterable, Function)
](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multimaps.html#index(java.lang.Iterable, com.google.common.base.Function))通常针对的场景是:有一组对象,它们有共同的特定属性,我们希望按照这个属性的值查询对象,但属性值不一定是独一无二的。
比方说,我们想把字符串按长度分组。
1 | ImmutableSet digits = ImmutableSet.of("zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"); |
invertFrom
鉴于Multimap可以把多个键映射到同一个值(译者注:实际上这是任何map都有的特性),也可以把一个键映射到多个值,反转Multimap也会很有用。Guava 提供了[invertFrom(Multimap toInvert,Multimap dest)
](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multimaps.html#invertFrom(com.google.common.collect.Multimap, M))做这个操作,并且你可以自由选择反转后的Multimap实现。
注:如果你使用的是ImmutableMultimap,考虑改用ImmutableMultimap.inverse()
做反转。
1 | ArrayListMultimap<String, Integer> multimap = ArrayListMultimap.create(); |
forMap
想在Map对象上使用Multimap的方法吗?forMap(Map)
把Map包装成SetMultimap。这个方法特别有用,例如,与Multimaps.invertFrom结合使用,可以把多对一的Map反转为一对多的Multimap。
1 | Map<String, Integer> map = ImmutableMap.of("a", 1, "b", 1, "c", 2); |
包装器
Multimaps提供了传统的包装方法,以及让你选择Map和Collection类型以自定义Multimap实现的工具方法。
只读包装 | Multimap |
ListMultimap |
SetMultimap |
SortedSetMultimap |
---|---|---|---|---|
同步包装 | Multimap |
ListMultimap |
SetMultimap |
SortedSetMultimap |
自定义实现 | [Multimap ](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multimaps.html#newMultimap(java.util.Map, com.google.common.base.Supplier)) |
[ListMultimap ](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multimaps.html#newListMultimap(java.util.Map, com.google.common.base.Supplier)) |
[SetMultimap ](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multimaps.html#newSetMultimap(java.util.Map, com.google.common.base.Supplier)) |
[SortedSetMultimap ](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Multimaps.html#newSortedSetMultimap(java.util.Map, com.google.common.base.Supplier)) |
自定义Multimap的方法允许你指定Multimap中的特定实现。但要注意的是:
- Multimap假设对Map和Supplier产生的集合对象有完全所有权。这些自定义对象应避免手动更新,并且在提供给Multimap时应该是空的,此外还不应该使用软引用、弱引用或虚引用。
- 无法保证修改了Multimap以后,底层Map的内容是什么样的。
- 即使Map和Supplier产生的集合都是线程安全的,它们组成的Multimap也不能保证并发操作的线程安全性。并发读操作是工作正常的,但需要保证并发读写的话,请考虑用同步包装器解决。
- 只有当Map、Supplier、Supplier产生的集合对象、以及Multimap存放的键值类型都是可序列化的,Multimap才是可序列化的。
- Multimap.get(key)返回的集合对象和Supplier返回的集合对象并不是同一类型。但如果Supplier返回的是随机访问集合,那么Multimap.get(key)返回的集合也是可随机访问的。
请注意,用来自定义Multimap的方法需要一个Supplier参数,以创建崭新的集合。下面有个实现ListMultimap的例子——用TreeMap做映射,而每个键对应的多个值用LinkedList存储。
1 | ListMultimap<String, Integer> myMultimap = Multimaps.newListMultimap( |
Tables
Tables
类提供了若干称手的工具方法。
自定义Table
堪比Multimaps.newXXXMultimap(Map, Supplier)工具方法,[Tables.newCustomTable(Map, Supplier)
](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/Tables.html#newCustomTable(java.util.Map, com.google.common.base.Supplier))允许你指定Table用什么样的map实现行和列。
1 | // 使用LinkedHashMaps替代HashMaps |
transpose
transpose(Table)
方法允许你把Table<C, R, V>
转置成Table<R, C, V>
。例如,如果你在用Table构建加权有向图,这个方法就可以把有向图反转。
包装器
还有很多你熟悉和喜欢的Table包装类。然而,在大多数情况下还请使用ImmutableTable
Unmodifiable | Table |
RowSortedTable |
---|---|---|
扩展集合工具类
简介
有时候你需要实现自己的集合扩展。也许你想要在元素被添加到列表时增加特定的行为,或者你想实现一个Iterable,其底层实际上是遍历数据库查询的结果集。Guava为你,也为我们自己提供了若干工具方法,以便让类似的工作变得更简单。(毕竟,我们自己也要用这些工具扩展集合框架。)
Forwarding装饰器
针对所有类型的集合接口,Guava都提供了Forwarding抽象类以简化装饰者模式的使用。
Forwarding抽象类定义了一个抽象方法:delegate(),你可以覆盖这个方法来返回被装饰对象。所有其他方法都会直接委托给delegate()。例如说:ForwardingList.get(int)实际上执行了delegate().get(int)。
通过创建ForwardingXXX的子类并实现delegate()方法,可以选择性地覆盖子类的方法来增加装饰功能,而不需要自己委托每个方法——译者注:因为所有方法都默认委托给delegate()返回的对象,你可以只覆盖需要装饰的方法。
此外,很多集合方法都对应一个”标准方法[standardxxx]”实现,可以用来恢复被装饰对象的默认行为,以提供相同的优点。比如在扩展AbstractList或JDK中的其他骨架类时,可以使用类似standardAddAll这样的方法。
让我们看看这个例子。假定你想装饰一个List,让其记录所有添加进来的元素。当然,无论元素是用什么方法——add(int, E), add(E), 或addAll(Collection)——添加进来的,我们都希望进行记录,因此我们需要覆盖所有这些方法。
1 | class AddLoggingList<E> extends ForwardingList<E> { |
记住,默认情况下,所有方法都直接转发到被代理对象,因此覆盖ForwardingMap.put并不会改变ForwardingMap.putAll的行为。小心覆盖所有需要改变行为的方法,并且确保装饰后的集合满足接口契约。
通常来说,类似于AbstractList的抽象集合骨架类,其大多数方法在Forwarding装饰器中都有对应的”标准方法”实现。
对提供特定视图的接口,Forwarding装饰器也为这些视图提供了相应的”标准方法”实现。例如,ForwardingMap提供StandardKeySet、StandardValues和StandardEntrySet类,它们在可以的情况下都会把自己的方法委托给被装饰的Map,把不能委托的声明为抽象方法。
PeekingIterator
有时候,普通的Iterator接口还不够。
Iterators提供一个Iterators.peekingIterator(Iterator)
方法,来把Iterator包装为PeekingIterator
,这是Iterator的子类,它能让你事先窥视[peek()
]到下一次调用next()返回的元素。
注意:Iterators.peekingIterator返回的PeekingIterator不支持在peek()操作之后调用remove()方法。
举个例子:复制一个List,并去除连续的重复元素。
1 | List<E> result = Lists.newArrayList(); |
传统的实现方式需要记录上一个元素,并在特定情况下后退,但这很难处理且容易出错。相较而言,PeekingIterator在理解和使用上就比较直接了。
AbstractIterator
实现你自己的Iterator?AbstractIterator
让生活更轻松。
用一个例子来解释AbstractIterator最简单。比方说,我们要包装一个iterator以跳过空值。
1 | public static Iterator<String> skipNulls(final Iterator<String> in) { |
你实现了computeNext()
方法,来计算下一个值。如果循环结束了也没有找到下一个值,请返回endOfData()表明已经到达迭代的末尾。
注意:AbstractIterator继承了UnmodifiableIterator,所以禁止实现remove()方法。如果你需要支持remove()的迭代器,就不应该继承AbstractIterator。
AbstractSequentialIterator
有一些迭代器用其他方式表示会更简单。AbstractSequentialIterator
就提供了表示迭代的另一种方式。
1 | Iterator<Integer> powersOfTwo = new AbstractSequentialIterator<Integer>(1) { // 注意初始值1! |
我们在这儿实现了computeNext(T)
方法,它能接受前一个值作为参数。
注意,你必须额外传入一个初始值,或者传入null让迭代立即结束。因为computeNext(T)假定null值意味着迭代的末尾——AbstractSequentialIterator不能用来实现可能返回null的迭代器。
非原创内容,转载自并发编程网 – ifeve.com。