集合(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()
和equals
与ImmutableSortedSet
基于比较器行为有不同的语义。
这将帮助良好的保护性程序风格的性能消耗减到最少程度。
asList
所有的不可变集合通过asList
提供了一个ImmutableList
视图。例如,即使你有数据按照ImmutableSortedSet
进行存储,你可以使用sortedSet.asList().get(k)
得到第k
个最小元素。
返回ImmutableList
是经常的(不一直是,但是经常)是一个常量开销视图,而不是显性复制。也就是说,它通常比一般的List
更聪明,–例如,你将使用备份集合的有效的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
传统的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的
-
- 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()
返回multiset不同元素的Set<E>
,像entrySet()
用于Map
一样。
-
Multiset
实现的内存消耗是按照不同元素的个数线性的。
特别是,Multiset
与Collection
接口的契约完全一致,除非在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 元素 |
---|---|---|
HashMap | HashMultiset | 是 |
TreeMap | TreeMultiset | 是 |
LinkedHashMap | LinkedHashMultiset | 是 |
ConcurrentHashMap | ConcurrentHashMultiset | 否 |
ImmutableMap | ImmutableMultiset | 否 |
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有变化则返回true | multimap.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不支持put
和putAll
。关键的是,当你在不存在的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
展示keys
将Multimap
的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()
方法。类似于存在于SetMultimap
和SortedSetMultimap
的方法)。 - 如果存在与指定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行为像… |
---|---|---|
ArrayListMultimap | HashMap | ArrayList |
HashMultimap | HashMap | HashSet |
LinkedListMultimap * | LinkedHashMap ,* | LinkedList ,* |
LinkedHashMultimap ** | LinkedHashMap | LinkedHashSet |
TreeMultimap | TreeMap | TreeSet |
ImmutableListMultimap | ImmutableMap | ImmutableList |
ImmutableSetMultimap | ImmutableMap | ImmutableSet |
除了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:
如果你尝试将一个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 Impl | Value-Key Map Impl | 相应的BiMap |
---|---|---|
HashMap | HashMap | HashBiMap |
ImmutableMap | ImmutableMap | ImmutableBiMap |
EnumMap | EnumMap | EnumBiMap |
EnumMap | HashMap | EnumHashBiMap |
注意:BiMap
实用工具像在Maps
中synchronizedBiMap
。
表(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提供有帮助的实现,名为MutableClassToInstanceMap
和ImmutableClassToInstanceMap
。
主要:像其他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后端端口都不支持RangeSet
;RangeSet
需要充分利用JDK1.6 中的NavigableMap
的特性。
视图
RangeSet
实现支持极其广的范围视图,包含:
complement()
:查看RangeSet
的补充。complement
也是一个RangeSet
,因为它包含不连接的,非空的范围。subRangeSet(Range<C>)
:返回RangeSet
与指定的Range
的交叉点视图。这样通用了传统排序集合的headSet
,subSet
,tailSet
视图。asRanges()
:将RangeSet
作为可被迭代的Set<Range<C>>
查看。asSet(DiscreteDomain<C>)
(仅仅ImmutableRangeSet
):将RangeSet<C>
作为ImmutableSortedSet<C>
查看,查看在范围内的元素而不是范围本身。(如果DiscreteDomain
和RangeSet
上下都是无界的,此操作是不支持的。)
查询
除了在它的视图上操作,RangeSet
直接支持几个查询操作,最突出的是:
contains(C)
:RangeSet
上最基本的操作,查询在RangeSet
中任意范围是否包含指定元素。rangeContaining(C)
:返回Range
,其封装指定的元素,如果没有则返回null
。encloses(Range<C>)
:够直接,测试在RangeSet
中的任意Range
是否封装指定的范围。span()
:返回在此RangeSet
中encloses
每一个范围的最小的Range
。
RangeMap
RangeMap
是一个集合类型描述从不连贯,非空范围对值的映射。不像RangeSet
,RangeMap
从不"合并"相邻的映射,尽管相邻的范围映射到相同的值。例如:
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
展示。通用了传统的headMap
,subMap
,tailMap
操作。
工具类
任何使用JDK集合框架有经验的程序员知道并喜欢在java.util.Collections
可用工具。Guava在这方便提供更多的实用工具:适用于所有集合的静态方法。这些是Guava最受欢迎和最成熟的部分。
对应于特定接口的方法以相对直观的方式分组:
接口 | JDK 或者Guava | 对应的Guava工具类 |
---|---|---|
Collection | JDK | Collections2 |
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 |
寻找转换过滤器等等?这些内容在我们的函数式编程章节,函数式语法下。
静态构造器
在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
有一些方便方法用于复制到不可变集合:
结果类型 | 方法 |
---|---|
ImmutableList | toImmutableList() |
ImmutableSet | toImmutableSet() |
ImmutableSortedSet | toImmutableSortedSet(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 ,float ) | Math.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 (即,使用myComparator 的MyType ) | 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
提供以下静态工厂方法:
实现 | 工厂 |
---|---|
HashSet | basic ,使用元素 ,从Iterable ,使用期望大小,从Iterator |
LinkedHashSet | basic ,从Iterable ,使用期望大小 |
TreeSet | basic ,使用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 |
EnumMap | 从Class ,从Map |
ConcurrentMap | 基本的 |
IdentityHashMap | 基本的 |
Multisets
标准的Collection
操作,例如containsAll
,忽略在multiset中元素的数量,只关心元素是否在multiset中。Multisets
提供多个考虑到元素多样性的操作。
方法 | 说明 | 与Collection 方法的不同 |
---|---|---|
containsOccurrences(Multiset sup, Multiset sub) | 对于所有元素o ,如果sub.count(o) <= super.count(o) 则返回true | Collection.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
提供传统的封装器方法,以及得到基于你选择的Map
和Collection
实现的自定义Multimap
实现工具。
Multimap类型 | 不可修改的 | 同步的 | 自定义的 |
---|---|---|---|
Multimap | unmodifiableMultimap | synchronizedMultimap | newMultimap |
ListMultimap | unmodifiableListMultimap | synchronizedListMultimap | newListMultimap |
SetMultimap | unmodifiableSetMultimap | synchronizedSetMultimap | newSetMultimap |
SortedSetMultimap | unmodifiableSortedSetMultimap | synchronizedSortedSetMultimap | newSortedSetMultimap |
自定义的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
,StandardValues
和StandardEntrySet
类,每个类尽可能地委托他们的方法到适配器map,否则,他们将不能委托的方法保留为抽象方法。
接口 | Forwarding适配器 |
---|---|
Collection | ForwardingCollection |
List | ForwardingList |
Set | ForwardingSet |
SortedSet | ForwardingSortedSet |
Map | ForwardingMap |
SortedMap | ForwardingSortedMap |
ConcurrentMap | ForwardingConcurrentMap |
Map.Entry | ForwardingMapEntry |
Queue | ForwardingQueue |
Iterator | ForwardingIterator |
ListIterator | ForwardingListIterator |
Multiset | ForwardingMultiset |
Multimap | ForwardingMultimap |
ListMultimap | ForwardingListMultimap |
SetMultimap | ForwardingSetMultimap |
PeekingIterator
有时普通的Interator
接口并不满足。
Interators
支持方法Iterators.peekingIterator(Iterator)
,它封装了Iterator
并返回PeekingIterator
,Iterator
子类型,允许你peek()
在下一次调用next()
时返回的元素。
注意:通过Iterators.peekingIterator
返回的PeekingIterator
在peek()
之后不支持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
迭代器。