文章目錄
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細節
接口 | JDK or 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 |
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最受歡迎和最成熟的部分。
與特定接口對應的方法以相對直觀的方式進行分組:
集合接口 | JDK or 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 |
尋找轉換,過濾器之類的東西?這些東西在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) |
返回對象的出現次數。 | Compare Collections.frequency(Collection, Object) ; see Multiset |
partition(Iterable, int) |
返回分區爲指定大小的塊的不可修改的iterable視圖 | Lists.partition(List, int) , paddedPartition(Iterable, int) |
getFirst(Iterable, T default) |
返回iterable的第一個元素,如果爲空,則返回默認值。 | Compare Iterable.iterator().next() , FluentIterable.first() |
getLast(Iterable) |
返回iterable的最後一個元素,如果爲空則快速失敗,並顯示NoSuchElementException 。 |
getLast(Iterable, T default) , FluentIterable.last() |
elementsEqual(Iterable, Iterable) |
如果iterable具有相同順序的相同元素,則返回true。 | Compare List.equals(Object) |
unmodifiableIterable(Iterable) |
返回不可修改的iterable視圖。 | Compare Collections.unmodifiableCollection(Collection) |
limit(Iterable, int) |
返回最多返回指定數量的元素的Iterable 。 |
FluentIterable.limit(int) |
getOnlyElement(Iterable) |
返回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!
3.2.2與Collection類似
通常,集合在其它集合上天然支持這些操作,但對iterables不提供支持。
當輸入實際上是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() |
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, with elements, from Iterable , with exact capacity, with expected size, from Iterator |
LinkedList |
basic, from Iterable |
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, with elements, from Iterable , with expected size, from Iterator |
LinkedHashSet |
basic, from Iterable , with expected size |
TreeSet |
basic, with Comparator , from Iterable |
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, from Map , with expected size |
LinkedHashMap |
basic, from Map |
TreeMap |
basic, from Comparator , from SortedMap |
EnumMap |
from Class , from Map |
ConcurrentMap |
basic |
IdentityHashMap |
basic |
3.5Multisets
標準Collection
操作(例如containsAll
)會忽略多重集合中元素的數量,而只關心元素是否在多重集合中。Multisets
提供了許多操作,這些操作考慮了多重集合中元素的重複性。
方法 | 說明 | 和Collection 方法的區別 |
---|---|---|
containsOccurrences(Multiset sup, Multiset sub) |
如果所有o 的sub.count(o) <= super.count(o) 則返回true 。 |
Collection.containsAll 忽略計數,僅測試是否包含元素。 |
removeOccurrences(Multiset removeFrom, Multiset toRemove) |
對於toRemove 中元素的每次出現,都刪除removeFrom 中的一個出現。 |
Collection.removeAll 刪除所有元素的所有出現,即使出現在toRemove 中也是如此。 |
retainOccurrences(Multiset removeFrom, Multiset toRetain) |
保證所有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