Google Guava Collections 使用介紹
簡介
Google Guava Collections 是一個對 Java Collections Framework 增強和擴充套件的一個開源專案。由於它高質量 API 的實現和對 Java特性的充分利用,使得其在 Java 社群受到很高評價。筆者主要介紹它的基本用法和功能特性。
使用介紹
-
Google Guava Collections(以下都簡稱為 Guava Collections)是 Java Collections Framework 的增強和擴充套件。每個 Java 開發者都會在工作中使用各種資料結構,很多情況下 Java Collections Framework 可以幫助你完成這類工作。
-
但是在有些場合你使用了 Java Collections Framework 的 API,但還是需要寫很多程式碼來實現一些複雜邏輯, 這個時候就可以嘗試使用 Guava Collections 來幫助你完成這些工作。這些高質量的 API 使你的程式碼更短,更易於閱 讀和修改,工作更加輕鬆。
目標讀者
- 對於理解 Java 開源工具來說,本文讀者至少應具備基礎的 Java 知識,特別是 JDK5 的特性。
- 因為 Guava Collections 充分使用了範型,迴圈增強這樣的特性。作為 Java Collections Framework 的增強,讀者必須對 Java Collections Framework 有清晰的理解,包括主要的介面約定和常用的實現類。
- 並且 Guava Collections 很大程度上是幫助開發者完成比較複雜的資料結構的操作,因此基礎的資料結構和演算法的知識也是清晰理解 Guava Collections 的 必要條件。
專案背景
-
Guava Collections 是 Google 的工程師 Kevin Bourrillion 和 Jared Levy 在著名"20%"時間寫的程式碼。當然作為開源專案還有其他的開發者貢獻了程式碼。
-
在編寫的過程中,Java Collections Framework 的作者 Joshua Bloch 也參與了程式碼稽核和提出建議。
-
目前它已經移到另外一個叫 guava-libraries 的開源專案下面來維護。 因為功能相似而且又同是開源專案,人們很很自然會把它和 Apache Commons Collections 來做比較。
集合介紹
-
Immutable Collections: 還在使用 Collections.unmodifiableXXX() ? Immutable Collections 這才是真正的不 可修改的集合 l Multiset: 看看如何把重複的元素放入一個集合。
-
Multimaps: 需要在一個 key 對應多個 value 的時候 , 自己寫一個實現比較繁瑣,讓 Multimaps 來幫忙
-
BiMap: java.util.Map 只能保證 key 的不重複,BiMap 保證 value 也不重複 l
-
MapMaker: 超級強大的 Map 構造類
-
Ordering class: 大家知道用 Comparator 作為比較器來對集合排序,但是對於多關鍵字排序 Ordering class 可以簡化很多的程式碼
Immutable Collections: 真正的不可修改的集合
- 大家都用過Collections.unmodifiableXXX() 來做一個不可修改的集合。例如你要構造儲存常量的Set。
Set<String> set = new HashSet<String>(Arrays.asList(new String[]{"RED", "GREEN"}));
Set<String> unmodifiableSet = Collections.unmodifiableSet(set);
-
每次調 unmodifiableSet.add() 會丟擲個 UnsupportedOperationException。
-
如果有人在原來的set上add或者remove元素會怎麼樣?結果unmodifiableSet也是被 add 或者 remove 元素了。
構造這樣一個簡單的set寫了兩句長的程式碼。下面看看ImmutableSet 是怎麼來做地更安全和簡潔 :
ImmutableSet<String> immutableSet = ImmutableSet.of("RED", "GREEN");
- 而且試圖調 add 方法的時候,它一樣會丟擲 UnsupportedOperationException,重要的是程式碼的可讀性增強了不少,非常直觀地展現了程式碼的用意。如果像之前這個程式碼保護一個set 怎麼做呢?**
ImmutableSet<String> immutableSet = ImmutableSet.copyOf(set);
從構造的方式來說,ImmutableSet 集合還提供了 Builder 模式來構造一個集合 :在這個例子裡面 Builder 不但能加入單個元素還能加入既有的集合。
Builder<String> builder = ImmutableSet.builder();
ImmutableSet<String> immutableSet = builder.add("RED").addAll(set).build();
Guava Collections 還提供了各種 Immutable 集合的實現
-
ImmutableList
-
ImmutableSet
ImmutableSet.of(4, 8, 15, 16, 23, 42);
ImmutableSet.copyOf(numbers);
- ImmutableSortedSet
- ImmutableMap
public static final ImmutableMap<String, Integer>
ENGLISH_TO_INT = ImmutableMap
.with("four", 4)
.with("eight", 8)
.with("fifteen", 15)
.with("sixteen", 16)
.with("twenty-three", 23)
.with("forty-two", 42)
.build();
ImmutableMap.of(1, "one", 2, "two");
- ImmutableSortedMap (one day)
Multiset: 把重複的元素放入集合
-
你可能會說這和 Set 介面的契約衝突,因為Set介面的 JavaDoc 裡面規定不能放入重複元素。事實上,Multiset 並 沒有實現 java.util.Set 介面,它更像是一個Bag。普通的 Set 就像這樣 :[car, ship, bike],而 Multiset 會是這樣 :
[car x 2, ship x 6, bike x 3]。 -
譬如一個 List 裡面有各種字串,然後你要統計每個字串在 List 裡面出現的次數:
Map<String, Integer> map = new HashMap<String, Integer>();
for(String word : wordList){
Integer count = map.get(word);
map.put(word, (count == null) ? 1 : count + 1);
}
//count word “the” Integer count = map.get(“the”);
如果用 Multiset 就可以這樣 :
HashMultiset<String> multiSet = HashMultiset.create();
multiSet.addAll(wordList);
//count word “the” Integer count = multiSet.count(“the”);
-
這樣連迴圈都不用了,而且 Multiset 用的方法叫 count,顯然比在 Map 裡面調 get 有更好的可讀性。
-
Multiset 還提供了 setCount 這樣設定元素重複次數的方法,雖然你可以通過使用 Map 來實現類似的功能,但是程式的可讀性比 Multiset 差了很多。
常用實現 Multiset 介面的類有:
- HashMultiset: 元素存放於HashMap
- LinkedHashMultiset: 元素存放於 LinkedHashMap,即元素的排列順序由第一次放入的順序決定
- TreeMultiset:元素被排序存放於TreeMap
- EnumMultiset: 元素必須是 enum 型別
- ImmutableMultiset: 不可修改的 Mutiset
- 看到這裡你可能已經發現 Guava Collections 都是以 create 或是 of 這樣的靜態方法來構造物件。這是因為這些集合
- 類大多有多個引數的私有構造方法,由於引數數目很多,客戶程式碼程式設計師使用起來就很不方便。而且以這種方式可以
- 返回原型別的子型別物件。另外,對於建立範型物件來講,這種方式更加簡潔。
看到這裡你可能已經發現 Guava Collections 都是以 create 或是 of 這樣的靜態方法來構造物件。這是因為這些集合類大多有多個引數的私有構造方法,由於引數數目很多,客戶程式碼程式設計師使用起來就很不方便。而且以這種方式可以
返回原型別的子型別物件。另外,對於建立範型物件來講,這種方式更加簡潔。
Multimap: 在 Map 的 value 裡面放多個元素
Multimap就是一個 key 對應多個 value 的資料結構。看上去它很像 java.util.Map 的結構,但是 Muitimap 不是 Map,沒有實現 Map 的介面。設想你對 Map 調了 2 次引數 key 一樣的 put 方法,結果就是第 2 次的 value 覆蓋
了第 1 次的 value。但是對 Muitimap 來說這個 key 同時對應了 2 個 value。所以 Map 看上去是 : {k1=v1, k2=v2,...},而 Muitimap 是 :{k1=[v1, v2, v3], k2=[v7, v8],....}。
- 舉個記名投票的例子,所有選票都放在一個 List
裡面,List 的每個元素包括投票人和選舉人的名字。
我們可以這樣寫 :
//Key is candidate name, its value is his voters
HashMap<String, HashSet<String>> hMap = new HashMap<String, HashSet<String>>();
for(Ticket ticket: tickets){
HashSet<String> set = hMap.get(ticket.getCandidate());
if(set == null){
set = new HashSet<String>();
hMap.put(ticket.getCandidate(), set);
}
set.add(ticket.getVoter());
}
我們再來看看 Muitimap 能做些什麼 :
HashMultimap<String, String> map = HashMultimap.create();
for(Ticket ticket: tickets){
map.put(ticket.getCandidate(), ticket.getVoter());
}
就這麼簡單! Muitimap 介面的主要實現類有:
- HashMultimap: key 放在 HashMap,而 value 放在 HashSet,即一個 key 對應的 value 不可重複
- ArrayListMultimap: key 放在 HashMap,而 value 放在 ArrayList,即一個 key 對應的 value 有順序可重複
- LinkedHashMultimap: key 放在 LinkedHashMap,而 value 放在 LinkedHashSet,即一個 key 對應的 value 有順序不可重複
- TreeMultimap: key 放在 TreeMap,而 value 放在 TreeSet,即一個 key 對應的 value 有排列順序
- ImmutableMultimap: 不可修改的 Multimap
BiMap: 雙向 Map
**BiMap 實現java.util.Map 介面。它的特點是它的value和它 key一樣也是不可重複的,換句話說它的 key 和value是等價的,如果你往 BiMap 的 value 裡面放了重複的元素,就會得到 IllegalArgumentException。 **
舉個例子,你可能經常會碰到在 Map 裡面根據 value 值來反推它的 key 值的邏輯:
for(Map.Entry<User, Address> entry : map.entreSet()){
if(entry.getValue().equals(anAddess)){
return entry.getKey();
}
}
return null;
- 如果把 User 和 Address 都放在 BiMap,那麼一句程式碼就得到結果了: return biMap.inverse().get(anAddess);
- 這裡的 inverse 方法就是把 BiMap 的 key 集合 value 集合對調,因此 biMap == biMap.inverse().inverse()。
BiMap的常用實現有:
- HashBiMap: key 集合與 value 集合都有 HashMap 實現
- EnumBiMap: key 與 value 都必須是 enum 型別
- ImmutableBiMap: 不可修改的 BiMap
MapMaker: 超級強大的 Map 構造工具
MapMaker 是用來構造 ConcurrentMap 的工具類。
為什麼可以把 MapMaker 叫做超級強大?看了下面的例子你就知道了。首先,它可以用來構造 ConcurrentHashMap:
//ConcurrentHashMap with concurrency level 8
ConcurrentMap<String, Object> map1 = new MapMaker().concurrencyLevel(8).makeMap();
或者構造用各種不同 reference 作為 key 和 value 的 Map:
//ConcurrentMap with soft reference key and weak reference value
ConcurrentMap<String, Object> map2 = new MapMaker().softKeys().weakValues().makeMap();
或者構造有自動移除時間過期項的 Map:
//Automatically removed entries from map after 30 seconds since they are created
ConcurrentMap<String, Object> map3 = new MapMaker()
.expireAfterWrite(30, TimeUnit.SECONDS)
.makeMap();
或者構造有最大限制數目的 Map:
//Map size grows close to the 100, the map will evict
//entries that are less likely to be used again
ConcurrentMap<String, Object> map4 = new MapMaker()
.maximumSize(100)
.makeMap();
或者提供當 Map 裡面不包含所 get 的項,而需要自動加入到 Map 的功能。這個功能當 Map 作為快取的時候很有 用 :
//Create an Object to the map, when get() is missing in map
ConcurrentMap<String, Object> map5 = new MapMaker().makeComputingMap(
new Function<String, Object>() {
public Object apply(String key) {
return createObject(key);
}});
這些還不是最強大的特性,最厲害的是 MapMaker 可以提供擁有以上所有特性的 Map:
//Put all features together!
ConcurrentMap<String, Object> mapAll = new MapMaker()
.concurrencyLevel(8)
.softKeys()
.weakValues()
.expireAfterWrite(30, TimeUnit.SECONDS)
.maximumSize(100)
.makeComputingMap(
new Function<String, Object>() {
public Object apply(String key) {
return createObject(key);
}});
Ordering class: 靈活的多欄位排序比較器
要對集合排序或者求最大值最小值,首推 java.util.Collections 類,但關鍵是要提供 Comparator 介面的實現。假設有 個待排序的 List
Collections.sort(list, new Comparator<Foo>(){
@Override
public int compare(Foo f1, Foo f2) {
int resultA = f1.a – f2.a;
int resultB = f1.b – f2.b;
return resultA == 0 ? (resultB == 0 ? f1.c – f2.c : resultB) : resultA;
}});
這看上去有點眼暈,如果用一串 if-else 也好不到哪裡去。看看 ComparisonChain 能做到什麼 :
Collections.sort(list, new Comparator<Foo>(){
@Override
return ComparisonChain.start()
.compare(f1.a, f2.a)
.compare(f1.b, f2.b)
.compare(f1.c, f2.c).result();
}});
如果排序關鍵字要用自定義比較器,compare 方法也有接受 Comparator 的過載版本。譬如 Foo 裡面每個排序關鍵字都已經有了各自的 Comparator,那麼利用 ComparisonChain 可以 :
Collections.sort(list, new Comparator<Foo>(){
@Override
return ComparisonChain.start()
.compare(f1.a, f2.a, comparatorA)
.compare(f1.b, f2.b, comparatorB)
.compare(f1.c, f2.c, comparatorC).result();
}});
Ordring 類還提供了一個組合 Comparator 物件的方法。而且 Ordring 本身實現了 Comparator 介面所以它能直接作 為 Comparator 使用:
Ordering<Foo> ordering = Ordering.compound(Arrays.asList(comparatorA, comparatorB, comparatorc));
Collections.sort(list, ordering);
過濾器(stream-filter)
利用 Collections2.filter() 方法過濾集合中不符合條件的元素。譬如過濾一個 List
元素 :
Collection<Integer> filterCollection =
Collections2.filter(list, new Predicate<Integer>(){
@Override
public boolean apply(Integer input) {
return input >= 10;
}});
-
當然,你可以自己寫一個迴圈來實現這個功能,但是這樣不能保證之後小於 10 的元素不被放入集合。
-
filter 的強大之 處在於返回的 filterCollection 仍然有排斥小於 10 的元素的特性,如果調 filterCollection.add(9) 就會得到一個IllegalArgumentException。
轉換器(Stream-map)
利用 Collections2.transform() 方法來轉換集合中的元素。譬如把一個 Set
Collection<String> formatCollection =
Collections2.transform(set, new Function<Integer, String>(){
@Override
public String apply(Integer input) {
return new DecimalFormat("#,###").format(input);
}} );
總結
以上介紹了 Guava Collections 的一些基本的功能特性。你可以從 guava-libraries 的官方網站下載它的 jar 包和它其他的相關文件。如果你使用 Maven 來管理你的專案依賴包,Maven 中央庫也提供了它版本的依賴。最後希望Guava Collections 使你的程式設計工作更輕鬆,更有樂趣。
使用
這個開源專案釋出的 jar 包可以在它的官方網站內(http://code.google.com/p/guava-libraries/downloads/list)找到。
其下載的 zip 包中含有 Guava Collections 的 jar 包 guava-r09.jar 及其依賴包 guava-r09-gwt.jar,javadoc,原始碼,readme 等檔案。使用時只需將 guava-r09.jar 和依賴包 guava-r09-gwt.jar 放入 CLASSPATH 中即可。
如果您使用 Maven 作為構建工具的話可以在 pom.xml 內加入:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>xxx</version>
</dependency>