Collections集合
Guava对JDK集合生态系统的扩展。这些是Guava中最成熟,最受欢迎的部分。
不可变集合,用于防御性编程,固定集合和提高效率。
新的集合类型,适用于JDK集合无法充分解决的用例:多重集合[multisets],多重映射[multimaps],表[tables],双向映射[bidirectional maps]等。
强大的集合工具,用于java.util.Collections
中未提供的常见操作。
扩展工具,写一个Collection
装饰器?实现Iterator
迭代器?我们可以使其变得更容易。
1.不可变集合
1.1示例
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!
}
}
1.2为什么使用
不可变对象具有许多优点,包括:
- 可供不受信任的库安全使用。
- 线程安全:可以被许多线程使用,而不会出现竞争状况。
- 不需要支持变动,并且可以节省时间和空间。所有不可变的集合实现比其可变的同级具有更高的内存效率。(分析)
- 可以作常量使用,它将保持不变。
制作对象的不变副本是一种很好的防御性编程技术。Guava提供每种标准Collection
类型的简单易用的不可变版本,包括Guava自己的Collection
变体。
JDK提供了Collections.unmodifiableXXX
方法,但我们认为,这些方法是
- 笨拙而冗长;在你想制作防御性副本的任何地方不能愉快使用
- 不安全:只有不存在对原始集合的引用的情况下,返回的集合才是真正不变的
- 低效:其数据结构仍然具有可变集合的所有开销,包括并发修改检查,哈希表中的额外空间等。
当你不希望修改集合或希望集合保持不变时,最好将其防御性地复制到不可变的集合中。
重要说明: 每个Guava不可变集合实现都拒绝null
空值。我们对Google的内部代码库进行了详尽的研究,结果表明,集合中大约有5%的时间允许使用null
元素,而其他95%的案例最好通过快速执行null失败来解决。如果需要使用null
空值,考虑在允许空值的集合实现中使用Collections.unmodifiableList
及其它用法。可以在此处找到更详细的建议。
1.3怎么用
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"的顺序遍历其元素。
1.3.1copyOf
比想象的更智能
这很有用去记住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
基于比较器的行为不同的语义。
这有助于最大程度地降低良好的防御性编程风格的性能开销。
1.3.2asList
所有不可变集合都通过asList()
提供一个ImmutableList
视图,因此——例如——即使你将数据存储为ImmutableSortedSet
,也可以使用sortedSet.asList().get(k)
获得第k
个最小的元素。
返回的ImmutableList
通常——并非总是但通常——是恒定开销的视图,而不是显式副本。就是说,它通常比一般List
列表更智能——例如,它将使用支持集合的高效contains
包含方法。
1.4细节
接口 | JDKorGuava? | 不可变版本 |
---|---|---|
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 |
2.新集合类型
Guava引入了许多新的不在JDK中的集合类型,但是我们发现它们非常有用。所有这些都旨在与JDK集合框架愉快地共存,而不会在JDK集合抽象中产生麻烦。
通常,Guava集合实现非常精确地遵循JDK接口协定。
2.1Multiset多重集合
传统的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
,它支持添加多个元素。维基百科在数学中将多集合定义为:“集合概念的概括,其中元素成员可以多次出现…在多重集合中,与集合[set]相同和元组[tuples]相反,元素的顺序无关紧要:多重集合{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
,计数为O(1),对于TreeMultiset
,计数为O(log n),依此类推。entrySet()
返回Set<Multiset.Entry<E>>
,其作用类似于Map
的entrySet
。elementSet()
返回多重集合的不同元素的Set<E>
,就像Map
的keySet()
一样。Multiset
实现的内存消耗在不同元素的数量上是线性的。
值得注意的是,Multiset
完全符合Collection
接口的约定,除非在少数情况下JDK本身有先例——特别是TreeMultiset
和TreeSet
一样,使用比较进行相等性而不是Object.equals
。特别是,Multiset.addAll(Collection)
每次出现时都会在Collection
中添加每个元素一次,这比上面Map
方法所需的for循环方便得多。
方法 | 描述 |
---|---|
count(E) | 计算已添加到此多集合的元素的出现次数。 |
elementSet() | 以Set<E>的形式查看Multiset<E>的不同元素。 |
entrySet() | 与Map.entrySet()类似,返回Set<Multiset.Entry<E>>,其中包含支持getElement()和getCount()的条目 |
add(E,int) | 添加指定元素的指定出现次数。 |
remove(E,int) | 删除指定元素的指定出现次数。 |
setCount(E,int) | 将指定元素的出现次数设置为指定的非负值。 |
size() | 返回Multiset中所有元素的出现总数。 |
2.1.1Multiset不是Map
请注意,Multiset<E>
不是Map<E, Integer>
,尽管它可能是Multiset
实现的一部分。多重集合Multiset
是真正的集合Collection
类型,并且满足所有相关的合同义务。其它显著差异包括:
Multiset<E>
的元素仅具有正计数。没有元素可以具有负计数,并且计数为0
的值被认为不在多重集合中。它们不会出现在elementSet()
或entrySet()
视图中。multiset.size()
返回集合的大小,该大小等于所有元素的计数之和。对于不同元素的数量,请使用elementSet().size()
。(因此,例如,add(E)
将multiset.size()
增加1。)multiset.iterator()
遍历每个元素的每次出现,因此迭代的长度等于multiset.size()
。Multiset<E>
支持添加元素,删除元素或直接设置元素次数。setCount(elem, 0)
等效于删除所有出现的元素。- 不在多重集合中的元素的
multiset.count(elem)
始终返回0
。
2.1.2实现
Guava提供了许多Multiset
的实现,这些实现大致对应于JDK map的实现。
Map | 对应的Multiset | 是否支持null元素 |
---|---|---|
HashMap | HashMultiset | Yes |
TreeMap | TreeMultiset | Yes |
LinkedHashMap | LinkedHashMultiset | Yes |
ConcurrentHashMap | ConcurrentHashMultiset | No |
ImmutableMap | ImmutableMultiset | No |
2.1.3SortedMultiset
SortedMultiset
是Multiset
接口上的一种变体,它支持高效地提取指定区间内的子集。例如,你可以使用latencies.subMultiset(0, BoundType.CLOSED, 100, BoundType.OPEN).size()
来确定你的网站在100ms延迟内的匹配数,然后将其与latencies.size()
进行比较确定整体比例。
TreeMultiset
实现SortedMultiset
接口。在撰写本文时,仍在测试ImmutableSortedMultiset
的GWT兼容性。
2.2Multimap多重映射
每个有经验的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
接口。通常,将使用ListMultimap
或SetMultimap
,它们分别将键映射到List
或Set
。
2.2.1构造
创建Multimap
的最直接的方法是使用MultimapBuilder
,它允许你配置键和值的表示方式。例如:
// 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
。
2.2.2修改
Multimap.get(key)
返回与指定键关联的值的视图,即使当前没有。对于ListMultimap
,它返回一个List
,对于SetMultimap
,它返回一个Set
。
修改会写入底层Multimap
。例如,
Set<Person> aliceChildren = childrenMultimap.get(alice);
aliceChildren.clear();
aliceChildren.add(bob);
aliceChildren.add(carol);
写入底层多重映射。
修改多重映射(更直接)的其它方法包括:
方法签名 | 描述 | 等价 |
---|---|---|
put(K,V) | 将键与值添加一个关联。 | multimap.get(key).add(value) |
putAll(K,Iterable) | 从键到每个值依次添加关联。 | Iterables.addAll(multimap.get(key),values) |
remove(K,V) | 从键key到值value中删除一个关联,并且如果多重映射集合更改了,则返回true。 | multimap.get(key).remove(value) |
removeAll(K) | 删除并返回与指定键关联的所有值。返回的集合可能可以修改,也可能无法修改,但是对其进行修改不会影响多重映射。(返回适当的集合类型。) | multimap.get(key).clear() |
replaceValues(K,Iterable) | 清除与键key关联的所有值,并将键key设置为与每个值values关联。返回先前与该键关联的值。 | multimap.get(key).clear();Iterables.addAll(multimap.get(key),values) |
2.2.3视图
Multimap
还支持许多强大的视图。
asMap
将任何Multimap<K, V>
视为Map<K, Collection<V>>
。返回的map映射支持remove
,并且对返回的集合所做的更改均会写入,但是该map映射不支持put
或putAll
。至关重要的是,当你要在不存在的键上使用null
而不是一个新的,可写的空集合时,可以使用asMap().get(key)
。(你可以并且应该将asMap.get(key)
强制转换为适当的集合类型——SetMultimap
的Set
,ListMultimap
的List
——但类型系统这里不允许ListMultimap
返回Map<K, List<V>>
。)entries
将Multimap
中的所有条目视为Collection<Map.Entry<K, V>>
。(对于SetMultimap
,这是一个Set
。)keySet
将Multimap
中的不同键视为一个Set
。keys
将Multimap
的键视为一个Multiset
,其多重性等于与该键关联的值的数量。元素可以从Multiset
中删除,但不能添加;更改将写入。values()
将Multimap
中的所有值视为“扁平”的Collection<V>
,全部当成一个集合。这类似于Iterables.concat(multimap.asMap().values())
,但返回完整的Collection
。
2.2.4Multimap不是Map
Multimap<K, V>
不是Map<K, Collection<V>>
,尽管这样的map映射可能在Multimap
实现中使用。显著差异包括:
Multimap.get(key)
始终返回一个非null,可能为空的集合。这并不意味着多重映射会花费与该键关联的任何内存,而是返回的集合是一个视图,该视图允许你根据需要添加与键的关联。- 如果你更喜欢类似
Map
的行为——不在多重映射中的键返回null
,请使用asMap()
视图获取Map<K, Collection<V>>
。(或者,要从ListMultimap
获取Map<K, List<V>>
,使用静态Multimaps.asMap()
方法。SetMultimap
和SortedSetMultimap
也存在类似的方法。) - 当且仅当与指定键有相关联的任何元素时,
Multimap.containsKey(key)
才为true。特别的,如果键k
先前与一个或多个值相关联,此后已从多重映射移除,则Multimap.containsKey(k)
将返回false。 Multimap.entries()
返回Multimap
中所有键的所有条目。如果要所有键->集合的条目,请使用asMap().entrySet()
。Multimap.size()
返回整个多重映射中的条目数,而不是不同键的数。使用Multimap.keySet().size()
来获取不同键的数量。
2.2.5实现
Multimap
提供了多种实现。你可以在大多数使用Map<K, Collection<V>>
的地方使用它。
实现 | 键的行为类似… | 值的行为类似… |
---|---|---|
ArrayListMultimap | HashMap | ArrayList |
HashMultimap | HashMap | HashSet |
LinkedListMultimap* | LinkedHashMap``* | LinkedList``* |
LinkedHashMultimap** | LinkedHashMap | LinkedHashSet |
TreeMultimap | TreeMap | TreeSet |
ImmutableListMultimap | ImmutableMap | ImmutableList |
ImmutableSetMultimap | ImmutableMap | ImmutableSet |
除了不可变的实现之外,这些实现中的每一个都支持null的键和值。
*
LinkedListMultimap.entries()
保留了 非不同 键值之间的迭代顺序。有关详细信息,请参见链接。
**
LinkedHashMultimap
保留条目的插入顺序,键的插入顺序以及与任何一个键关联的值的集合的插入顺序。
请注意,并非所有实现都实际上与列出的实现一样作为Map<K, Collection<V>>
来实现的!(特别是,一些Multimap
实现使用自定义哈希表来最大程度地减少开销。)
如果需要更多自定义,请使用Multimaps.newMultimap(Map, Supplier)
或list列表和set集合版本,以使用自定义集合、列表或集合实现来支持多重映射。
2.3BiMap双向映射
将值映射回键的传统方法是维护两个单独的映射,并使两个映射保持同步,但这容易出错,并且在映射中已经存在值时会造成极大的混乱。例如:
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<K, V>
如果你尝试将键映射到已经存在的值,则BiMap.put(key, value)
将抛出IllegalArgumentException
。如果要删除具有指定值的任何现有条目,改用BiMap.forcePut(key, value)
。
BiMap<String, Integer> userId = HashBiMap.create();
...
String userForId = userId.inverse().get(id);
2.3.1实现
键-值映射实现 | 值-键映射实现 | 对应的BiMap |
---|---|---|
HashMap | HashMap | HashBiMap |
ImmutableMap | ImmutableMap | ImmutableBiMap |
EnumMap | EnumMap | EnumBiMap |
EnumMap | HashMap | EnumHashBiMap |
注意: BiMap
工具,例如,synchronizedBiMap
位于Maps
中。
2.4Table
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
通常,当你尝试一次在多个键上建立索引时,会遇到诸如Map<FirstName, Map<LastName, Person>>
之类的东西,使用起来很丑陋且笨拙。Guava提供了一个新的集合类型Table
,它支持任何“行”类型和“列”类型的用例。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.Cell<R, C, V>
的集合形式返回Table
的视图。Cell
单元格非常类似于Map.Entry
,但是区分行键和列键。
提供了几种Table
表格实现,包括:
HashBasedTable
,本质上由HashMap<R, HashMap<C, V>>
支持。TreeBasedTable
,本质上由TreeMap<R, TreeMap<C, V>>
支持。ImmutableTable
ArrayTable
,它要求在构造时指定行和列的完整范围,但由二维数组支持,以在表密集时提高速度和内存效率。ArrayTable
的工作方式与其它实现有所不同。有关详细信息,请查阅Javadoc。
2.5ClassToInstanceMap
有时,你的映射键并非全都属于同一类型:它们的键是类型,并且你想将它们映射到该类型的值。Guava为此提供了ClassToInstanceMap
。
除了扩展Map
接口之外,ClassToInstanceMap
还提供了方法 T getInstance(Class)
和T putInstance(Class, 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
可能包含原始类型的条目,并且原始类型及其对应的包装器类型可以映射到不同的值。
2.6RangeSet
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或JDK 1.5及更早版本都不支持RangeSet
。RangeSet
需要充分利用JDK 1.6中的NavigableMap
功能。
2.6.1视图
RangeSet
实现支持非常广泛的视图,包括:
complement()
:RangeSet
的补集视图。complement
也是一个RangeSet
,因为它包含不相连的非空区间。subRangeSet(Range<C>)
:返回RangeSet
与指定Range
的交集的视图。这归纳了传统排序集合的headSet
,subSet
和tailSet
视图。asRanges()
:将RangeSet
查看为Set<Range<C>>
,可以对其进行迭代。asSet(DiscreteDomain)
(仅ImmutableRangeSet
支持):将RangeSet<C>
查看为ImmutableSortedSet<C>
,查看区间中的元素而不是区间本身。(如果DiscreteDomain
和RangeSet
都没有上边界或都没有下边界,则不支持此操作。)
2.6.2查询
除了对其视图进行操作外,RangeSet
还直接支持多种查询操作,其中最突出的是:
contains(C)
:RangeSet
上最基本的操作,查询RangeSet
中的任何区间是否包含指定的元素。rangeContaining(C)
:返回包含指定元素的Range
;如果没有,则返回null
。encloses(Range<C>)
:足够直接地,测试RangeSet
中的任何Range
是否包含指定区间。span()
:返回encloses
包含此RangeSet
中每个区间的最小Range
。
2.7RangeMap
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"}
2.7.1视图
RangeMap
提供两种视图:
asMapOfRanges()
:将RangeMap
视作Map<Range<K>, V>
。例如,这可以用于迭代RangeMap
。subRangeMap(Range<K>)
会将RangeMap
与指定Range
的交集作为RangeMap
视图。这归纳了传统的headMap
,subMap
和tailMap
操作。
3.集合工具
任何具有JDK集合框架经验的程序员都知道并喜欢java.util.Collections
中提供的工具。Guava沿这些思路提供了更多工具:适用于所有集合的静态方法。这些是Guava最受欢迎和最成熟的部分。
与特定接口对应的方法以相对直观的方式进行分组:
集合接口 | JDKorGuava? | 对应的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 |
寻找转换,过滤器之类的东西?这些东西在functional函数式语法下的函数式编程文章中。
3.1静态构造函数
在JDK 7之前,构造新的泛型集合需要令人不愉快的重复代码:
List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<TypeThatsTooLongForItsOwnGood>();
我认为我们都可以同意这是令人不愉快的。 Guava提供了使用泛型来推断右侧类型的静态方法:
List<TypeThatsTooLongForItsOwnGood> list = Lists.newArrayList();
Map<KeyType, LongishValueType> map = Maps.newLinkedHashMap();
可以肯定的是,JDK 7中的菱形运算符使此工作不再那么麻烦:
List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<>();
但是Guava的发展远不止于此。使用工厂方法模式,我们可以非常方便地使用其起始元素初始化集合。
Set<Type> copySet = Sets.newHashSet(elements);
List<String> theseElements = Lists.newArrayList("alpha", "beta", "gamma");
此外,借助命名工厂方法的功能(Effective Java项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();
3.2Iterables
可能情况下,Guava都倾向于提供接受Iterable
而不是Collection
的工具。在Google,遇到实际上没有存储在主存储器中的“集合”并不少见,而是从数据库或另一个数据中心收集的,并且如果不实际获取所有元素就无法支持size()
之类的操作。
因此,可以在Iterables
中找到你可能希望看到的所有集合都支持的许多操作。此外,大多数Iterators
方法在Iterators
中都有一个对应的版本,可以接受原始的Iterator。
Iterables
类中的绝大多数操作都是懒惰的:它们仅在绝对必要时才推进支持迭代。本身返回Iterables
的方法将返回延迟计算的视图,而不是在内存中显式构造一个集合。
从Guava 12开始,FluentIterable
类对Iterables
进行了补充,该类包装了Iterable
,并为其中的许多操作提供了“流利”的语法。
以下是一些最常用的工具的选择,尽管在Guava functional idioms中讨论了Iterables
中许多更“函数式”的方法。
3.2.1常规方法
方法 | 描述 | 参见 |
---|---|---|
concat(Iterable) | 返回几个iterable的串联的惰性视图。 | concat(Iterable...) |
frequency(Iterable,Object) | 返回对象的出现次数。 | CompareCollections.frequency(Collection,Object);seeMultiset |
partition(Iterable,int) | 返回分区为指定大小的块的不可修改的iterable视图 | Lists.partition(List,int),paddedPartition(Iterable,int) |
getFirst(Iterable,Tdefault) | 返回iterable的第一个元素,如果为空,则返回默认值。 | CompareIterable.iterator().next(),FluentIterable.first() |
getLast(Iterable) | 返回iterable的最后一个元素,如果为空则快速失败,并显示NoSuchElementException。 | getLast(Iterable,Tdefault),FluentIterable.last() |
elementsEqual(Iterable,Iterable) | 如果iterable具有相同顺序的相同元素,则返回true。 | CompareList.equals(Object) |
unmodifiableIterable(Iterable) | 返回不可修改的iterable视图。 | CompareCollections.unmodifiableCollection(Collection) |
limit(Iterable,int) | 返回最多返回指定数量的元素的Iterable。 | FluentIterable.limit(int) |
getOnlyElement(Iterable) | 返回Iterable中的唯一元素。如果iterable为空或包含多个元素,则会快速失败。 | getOnlyElement(Iterable,Tdefault) |
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!
3.2.2与Collection类似
通常,集合在其它集合上天然支持这些操作,但对iterables不提供支持。
当输入实际上是Collection
时,这些操作中的每一个都委托给相应的Collection
接口方法。例如,如果将Iterables.size
传递给Collection
,它将调用Collection.size
方法,而不是遍历迭代器。
方法 | 类似的Collection方法 | 等价的FluentIterable |
---|---|---|
addAll(CollectionaddTo,IterabletoAdd) | Collection.addAll(Collection) | |
contains(Iterable,Object) | Collection.contains(Object) | FluentIterable.contains(Object) |
removeAll(IterableremoveFrom,CollectiontoRemove) | Collection.removeAll(Collection) | |
retainAll(IterableremoveFrom,CollectiontoRetain) | 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() |
3.2.3FluentIterable
除了上面介绍的方法以及在函数式语法的functional中,FluentIterable
还提供了一些方便的方法来复制到不可变的集合中:
结果类型 | 方法 |
---|---|
ImmutableList | toImmutableList() |
ImmutableSet | toImmutableSet() |
ImmutableSortedSet | toImmutableSortedSet(Comparator) |
3.2.4Lists
除了静态构造函数方法和函数编程方法外,Lists
还为List
对象提供了许多有价值的工具方法。
方法 | 描述 |
---|---|
partition(List,int) | 返回基础列表的视图,该视图分为指定大小的块。 |
reverse(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}}
3.2.5静态工厂方法
Lists
提供了以下静态工厂方法:
实现 | 工厂 |
---|---|
ArrayList | basic,withelements,fromIterable,withexactcapacity,withexpectedsize,fromIterator |
LinkedList | basic,fromIterable |
3.3Sets
Sets
工具类包括许多好用的方法。
3.3.1集合理论运算
我们提供了一些标准的集合理论运算,作为参数集的视图来实现。这些返回一个SetView
,可以使用:
- 直接作为
Set
,因为它实现了Set
接口 - 通过使用
copyInto(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();
3.3.2其它Set工具
方法 | 描述 | 参见 |
---|---|---|
cartesianProduct(List) | 返回每个可能的列表,可以通过从每个集合中选择一个元素来获得。 | 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"}}
3.3.3静态工厂方法
Sets
提供以下静态工厂方法:
实现 | 工厂方法 |
---|---|
HashSet | basic,withelements,fromIterable,withexpectedsize,fromIterator |
LinkedHashSet | basic,fromIterable,withexpectedsize |
TreeSet | basic,withComparator,fromIterable |
3.4Maps
Maps
有许多很酷的工具,值得单独解释。
3.4.1uniqueIndex
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
。
3.4.2difference
Maps.difference(Map, Map)
允许你比较两个map之间的所有差异。它返回一个MapDifference
对象,该对象将维恩图分解为:
方法 | 描述 |
---|---|
entriesInCommon() | 这两个映射中的条目均具有匹配的键和值。 |
entriesDiffering() | 条目具有相同的键,但值不同。此映射中的值的类型为MapDifference.ValueDifference,可让你查看左侧和右侧的值。 |
entriesOnlyOnLeft() | 返回其键在左侧但不在右侧映射中的条目。 |
entriesOnlyOnRight() | 返回其键在右侧但不在左侧映射中的条目。 |
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}
3.4.3BiMap
工具
BiMap
上的Guava工具位于Maps
类中,因为BiMap
也是Map
。
BiMap工具 | 相应的Map工具 |
---|---|
synchronizedBiMap(BiMap) | Collections.synchronizedMap(Map) |
unmodifiableBiMap(BiMap) | Collections.unmodifiableMap(Map) |
3.4.3.1静态工厂方法
Maps
提供以下静态工厂方法。
实现 | 工厂方法 |
---|---|
HashMap | basic,fromMap,withexpectedsize |
LinkedHashMap | basic,fromMap |
TreeMap | basic,fromComparator,fromSortedMap |
EnumMap | fromClass,fromMap |
ConcurrentMap | basic |
IdentityHashMap | basic |
3.5Multisets
标准Collection
操作(例如containsAll
)会忽略多重集合中元素的数量,而只关心元素是否在多重集合中。Multisets
提供了许多操作,这些操作考虑了多重集合中元素的重复性。
方法 | 说明 | 和Collection方法的区别 |
---|---|---|
containsOccurrences(Multisetsup,Multisetsub) | 如果所有o的sub.count(o)<=super.count(o)则返回true。 | Collection.containsAll忽略计数,仅测试是否包含元素。 |
removeOccurrences(MultisetremoveFrom,MultisettoRemove) | 对于toRemove中元素的每次出现,都删除removeFrom中的一个出现。 | Collection.removeAll删除所有元素的所有出现,即使出现在toRemove中也是如此。 |
retainOccurrences(MultisetremoveFrom,MultisettoRetain) | 保证所有o的removeFrom.count(o)<=toRetain.count(o). | Collection.retainAll保留所有元素的所有出现,即使出现在toRetain中也是如此。 |
intersection(Multiset,Multiset) | 返回两个多重集合交集的视图;一种非破坏性的方法来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
multiset2.removeOccurrences(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) | 返回按频率降序遍历元素的多重集合的不变副本。 |
unmodifiableMultiset(Multiset) | 返回多重集合的不可修改视图。 |
unmodifiableSortedMultiset(SortedMultiset) | 返回排序后的多重集合的不可修改视图。 |
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"}
3.6Multimaps
Multimaps
提供了一些常规工具操作,值得单独解释。
3.6.1index
当你希望能够查找具有某些共同特定属性(不一定是唯一属性)的所有对象时,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"}
*/
3.6.2invertFrom
由于Multimap
可以将多个键映射到一个值,而一个键则映射到多个值,因此反转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.<String, Integer> 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"}
*/
3.6.3forMap
是否需要在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"}]
3.6.4包装器
Multimaps
提供了传统的包装方法,以及用于根据你选择的Map
和Collection
实现获取自定义Multimap
实现的工具。
Multimap类型 | 不可变包装 | 同步包装 | 自定义包装 |
---|---|---|---|
Multimap | unmodifiableMultimap | synchronizedMultimap | newMultimap |
ListMultimap | unmodifiableListMultimap | synchronizedListMultimap | newListMultimap |
SetMultimap | unmodifiableSetMultimap | synchronizedSetMultimap | newSetMultimap |
SortedSetMultimap | unmodifiableSortedSetMultimap | synchronizedSortedSetMultimap | newSortedSetMultimap |
自定义Multimap
实现可让你指定应在返回的Multimap
中使用特定实现。注意事项包括:
- 多重映射假定对map和工厂返回的列表拥有完全所有权。这些对象不应手动更新,提供时应为空,并且不应使用软引用、弱引用或虚引用。
- 修改
Multimap
后, 无法保证Map
的内容是什么样的。 - 当任何并发操作更新多重映射时,即使map和工厂生成的实例都是多重映射,也不是线程安全的。但是,并发读操作将正常运行。如有必要,请使用
synchronized
同步包装器解决此问题。 - 如果map、工厂、工厂生成的列表以及多重集合的内容都可序列化,则多重集合可序列化。
Multimap.get(key)
返回的集合与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();
}
});
3.7Tables
Tables
类提供了一些方便的工具。
3.7.1customTable
与Multimaps.newXXXMultimap(Map, Supplier)
工具相比,Tables.newCustomTable(Map, Supplier)
允许你使用所需的任何行或列映射来指定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();
}
});
3.7.2transpose
使用transpose(Table)
方法可以将Table<R, C, V>
视为Table<C, R, V>
。
3.7.3包装器
这些是你熟悉和喜爱的不可修改的包装器。但是,请考虑在大多数情况下改用ImmutableTable
。
4.扩展工具
4.1简介
有时你需要编写自己的集合扩展。也许你希望在将元素添加到列表中时添加特殊的行为,或者你想要编写实际上由数据库查询支持的Iterable
。Guava提供了许多工具,可以使你和我们都更轻松地完成这些任务。(毕竟,我们自己负责扩展集合框架。)
4.2Forwarding Decorators
对于所有各种集合接口,Guava提供了Forwarding
抽象类以简化使用装饰器模式decorator pattern。
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 |
4.3PeekingIterator
有时,普通的Iterator
接口还不够。
Iterators
迭代器支持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);
}
传统的方式是记录先前的元素,并在某些条件下回退,但这是一项棘手且容易出错的业务。PeekingIterator
相对容易理解和使用。
4.4AbstractIterator
实现自己的Iterator
?AbstractIterator
可以使你的生活更轻松。
举个例子最容易解释。假设我们要包装一个iterator迭代器,以跳过空值。
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
。
4.4.1AbstractSequentialIterator
一些迭代器更容易以其他方式表示。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)
方法,该方法接受previous的值作为参数。
请注意,你还必须另外传递一个初始值;如果迭代器应立即结束,则应传递null
。
请注意,computeNext
假设null
值表示迭代结束——AbstractSequentialIterator
不能用于实现可能返回null
的迭代器。
本文参考:
Immutable collections
New collection types
Powerful collection utilities
Extension utilities
guava-tests-collect
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。