Google Guava與集合操作相關的類

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>>,其作用類似於MapentrySet
    • elementSet()返回多重集合的不同元素的Set<E>,就像MapkeySet()一樣。
    • Multiset實現的內存消耗在不同元素的數量上是線性的。

值得注意的是,Multiset完全符合Collection接口的約定,除非在少數情況下JDK本身有先例——特別是TreeMultisetTreeSet一樣,使用比較進行相等性而不是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

SortedMultisetMultiset接口上的一種變體,它支持高效地提取指定區間內的子集。例如,你可以使用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接口。通常,將使用ListMultimapSetMultimap,它們分別將鍵映射到ListSet

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映射不支持putputAll。至關重要的是,當你要在不存在的鍵上使用null而不是一個新的,可寫的空集合時,可以使用asMap().get(key)。(你可以並且應該將asMap.get(key)強制轉換爲適當的集合類型——SetMultimapSetListMultimapList——但類型系統這裏不允許ListMultimap返回Map<K, List<V>>。)
  • entriesMultimap中的所有條目視爲Collection<Map.Entry<K, V>>。(對於SetMultimap,這是一個Set。)
  • keySetMultimap中的不同鍵視爲一個Set
  • keysMultimap的鍵視爲一個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()方法。SetMultimapSortedSetMultimap也存在類似的方法。)
  • 當且僅當與指定鍵有相關聯的任何元素時,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>

  • 允許使用inverse()查看“反轉”的BiMap<V, K>
  • 確保值是唯一的,使values()成爲Set

如果你嘗試將鍵映射到已經存在的值,則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提供了一些有用的實現,分別命名爲MutableClassToInstanceMapImmutableClassToInstanceMap

重要說明: 與任何其它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及更早版本都不支持RangeSetRangeSet需要充分利用JDK 1.6中的NavigableMap功能。

2.6.1視圖

RangeSet實現支持非常廣泛的視圖,包括:

  • complement()RangeSet的補集視圖。complement也是一個RangeSet,因爲它包含不相連的非空區間。
  • subRangeSet(Range<C>):返回RangeSet與指定Range的交集的視圖。這歸納了傳統排序集合的headSetsubSettailSet視圖。
  • asRanges():將RangeSet查看爲Set<Range<C>>,可以對其進行迭代。
  • asSet(DiscreteDomain) (僅ImmutableRangeSet支持):將RangeSet<C>查看爲ImmutableSortedSet<C>,查看區間中的元素而不是區間本身。(如果DiscreteDomainRangeSet都沒有上邊界或都沒有下邊界,則不支持此操作。)

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視圖。這歸納了傳統的headMapsubMaptailMap操作。

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) 如果所有osub.count(o) <= super.count(o)則返回true Collection.containsAll忽略計數,僅測試是否包含元素。
removeOccurrences(Multiset removeFrom, Multiset toRemove) 對於toRemove中元素的每次出現,都刪除removeFrom中的一個出現。 Collection.removeAll刪除所有元素的所有出現,即使出現在toRemove中也是如此。
retainOccurrences(Multiset removeFrom, Multiset toRetain) 保證所有oremoveFrom.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.uniqueIndexMultimaps.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提供了傳統的包裝方法,以及用於根據你選擇的MapCollection實現獲取自定義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提供了StandardKeySetStandardValuesStandardEntrySet類,每一個類都儘可能將其方法委託給經過修飾的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

實現自己的IteratorAbstractIterator可以使你的生活更輕鬆。

舉個例子最容易解釋。假設我們要包裝一個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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章