Google Guava Collections(以下都簡(jiǎn)稱為 Guava Collections)是 Java Collections Framework 的增強(qiáng)和擴(kuò)展。每個(gè) Java 開發(fā)者都會(huì)在工作中使用各種數(shù)據(jù)結(jié)構(gòu),很多情況下 Java Collections Framework 可以幫助你完成這類工作。但是在有些場(chǎng)合你使用了 Java Collections Framework 的 API,但還是需要寫很多代碼來實(shí)現(xiàn)一些復(fù)雜邏輯,這個(gè)時(shí)候就可以嘗試使用 Guava Collections 來幫助你完成這些工作。這些高質(zhì)量的 API 使你的代碼更短,更易于閱讀和修改,工作更加輕松。
對(duì)于理解 Java 開源工具來說,本文讀者至少應(yīng)具備基礎(chǔ)的 Java 知識(shí),特別是 JDK5 的特性。因?yàn)?Guava Collections 充分使用了范型,循環(huán)增強(qiáng)這樣的特性。作為 Java Collections Framework 的增強(qiáng),讀者必須對(duì) Java Collections Framework 有清晰的理解,包括主要的接口約定和常用的實(shí)現(xiàn)類。并且 Guava Collections 很大程度上是幫助開發(fā)者完成比較復(fù)雜的數(shù)據(jù)結(jié)構(gòu)的操作,因此基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)和算法的知識(shí)也是清晰理解 Guava Collections 的必要條件。
Guava Collections 是 Google 的工程師 Kevin Bourrillion 和 Jared Levy 在著名"20%"時(shí)間寫的代碼。當(dāng)然作為開源項(xiàng)目還有其他的開發(fā)者貢獻(xiàn)了代碼。在編寫的過程中,Java Collections Framework 的作者 Joshua Bloch 也參與了代碼審核和提出建議。目前它已經(jīng)移到另外一個(gè)叫 guava-libraries 的開源項(xiàng)目下面來維護(hù)。
因?yàn)楣δ芟嗨贫矣滞情_源項(xiàng)目,人們很很自然會(huì)把它和 Apache Commons Collections 來做比較。其區(qū)別歸結(jié)起來有以下幾點(diǎn):
Guava Collections 充分利用了 JDK5 的范型和枚舉這樣的特性,而 Apache Commons Collections 則是基于 JDK1.2。其次 Guava Collections 更加嚴(yán)格遵守 Java Collections Framework 定義的接口契約,而在 Apache Commons Collections 你會(huì)發(fā)現(xiàn)不少違反這些 JDK 接口契約的地方。這些不遵守標(biāo)準(zhǔn)的地方就是出 bug 的風(fēng)險(xiǎn)很高。最后 Guava Collections 處于積極的維護(hù)狀態(tài),本文介紹的特性都基于目前最新 2011 年 4 月的 Guava r09 版本,而 Apache Commons Collections 最新一次發(fā)布也已經(jīng)是 2008 年了。
可以說 Java Collections Framework 滿足了我們大多數(shù)情況下使用集合的要求,但是當(dāng)遇到一些特殊的情況我們的代碼會(huì)比較冗長(zhǎng),比較容易出錯(cuò)。Guava Collections 可以幫助你的代碼更簡(jiǎn)短精煉,更重要是它增強(qiáng)了代碼的可讀性。看看 Guava Collections 為我們做了哪些很酷的事情。
當(dāng)然,如果沒有 Guava Collections 你也可以用 Java Collections Framework 完成上面的功能。但是 Guava Collections 提供的這些 API 經(jīng)過精心設(shè)計(jì),而且還有 25000 個(gè)單元測(cè)試來保障它的質(zhì)量。所以我們沒必要重新發(fā)明輪子。接下來我們來詳細(xì)看看 Guava Collections 的一些具體功能。
大家都用過 Collections.unmodifiableXXX() 來做一個(gè)不可修改的集合。例如你要構(gòu)造存儲(chǔ)常量的 Set,你可以這樣來做 :
Set<String> set = new HashSet<String>(Arrays.asList(new String[]{"RED", "GREEN"})); Set<String> unmodifiableSet = Collections.unmodifiableSet(set);
這看上去似乎不錯(cuò),因?yàn)槊看握{(diào) unmodifiableSet.add() 都會(huì)拋出一個(gè) UnsupportedOperationException。感覺安全了?慢!如果有人在原來的 set 上 add 或者 remove 元素會(huì)怎么樣?結(jié)果 unmodifiableSet 也是被 add 或者 remove 元素了。而且構(gòu)造這樣一個(gè)簡(jiǎn)單的 set 寫了兩句長(zhǎng)的代碼。下面看看 ImmutableSet 是怎么來做地更安全和簡(jiǎn)潔 :
ImmutableSet<String> immutableSet = ImmutableSet.of("RED", "GREEN");
就這樣一句就夠了,而且試圖調(diào) add 方法的時(shí)候,它一樣會(huì)拋出 UnsupportedOperationException。重要的是代碼的可讀性增強(qiáng)了不少,非常直觀地展現(xiàn)了代碼的用意。如果像之前這個(gè)代碼保護(hù)一個(gè) set 怎么做呢?你可以 :
ImmutableSet<String> immutableSet = ImmutableSet.copyOf(set);
從構(gòu)造的方式來說,ImmutableSet 集合還提供了 Builder 模式來構(gòu)造一個(gè)集合 :
Builder<String> builder = ImmutableSet.builder(); ImmutableSet<String> immutableSet = builder.add("RED").addAll(set).build();
在這個(gè)例子里面 Builder 不但能加入單個(gè)元素還能加入既有的集合。
除此之外,Guava Collections 還提供了各種 Immutable 集合的實(shí)現(xiàn):ImmutableList,ImmutableMap,ImmutableSortedSet,ImmutableSortedMap。
你可能會(huì)說這和 Set 接口的契約沖突,因?yàn)?Set 接口的 JavaDoc 里面規(guī)定不能放入重復(fù)元素。事實(shí)上,Multiset 并沒有實(shí)現(xiàn) java.util.Set 接口,它更像是一個(gè) Bag。普通的 Set 就像這樣 :[car, ship, bike],而 Multiset 會(huì)是這樣 : [car x 2, ship x 6, bike x 3]。
譬如一個(gè) List 里面有各種字符串,然后你要統(tǒng)計(jì)每個(gè)字符串在 List 里面出現(xiàn)的次數(shù) :
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”);
這樣連循環(huán)都不用了,而且 Multiset 用的方法叫 count,顯然比在 Map 里面調(diào) get 有更好的可讀性。Multiset 還提供了 setCount 這樣設(shè)定元素重復(fù)次數(shù)的方法,雖然你可以通過使用 Map 來實(shí)現(xiàn)類似的功能,但是程序的可讀性比 Multiset 差了很多。
常用實(shí)現(xiàn) Multiset 接口的類有:
元素被排序存放于
TreeMap看到這里你可能已經(jīng)發(fā)現(xiàn) Guava Collections 都是以 create 或是 of 這樣的靜態(tài)方法來構(gòu)造對(duì)象。這是因?yàn)檫@些集合類大多有多個(gè)參數(shù)的私有構(gòu)造方法,由于參數(shù)數(shù)目很多,客戶代碼程序員使用起來就很不方便。而且以這種方式可以返回原類型的子類型對(duì)象。另外,對(duì)于創(chuàng)建范型對(duì)象來講,這種方式更加簡(jiǎn)潔。
Muitimap 就是一個(gè) key 對(duì)應(yīng)多個(gè) value 的數(shù)據(jù)結(jié)構(gòu)??瓷先ニ芟?java.util.Map 的結(jié)構(gòu),但是 Muitimap 不是 Map,沒有實(shí)現(xiàn) Map 的接口。設(shè)想你對(duì) Map 調(diào)了 2 次參數(shù) key 一樣的 put 方法,結(jié)果就是第 2 次的 value 覆蓋了第 1 次的 value。但是對(duì) Muitimap 來說這個(gè) key 同時(shí)對(duì)應(yīng)了 2 個(gè) value。所以 Map 看上去是 : {k1=v1, k2=v2,...},而 Muitimap 是 :{k1=[v1, v2, v3], k2=[v7, v8],....}。
舉個(gè)記名投票的例子。所有選票都放在一個(gè) List<Ticket> 里面,List 的每個(gè)元素包括投票人和選舉人的名字。我們可以這樣寫 :
//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()); }
我們?cè)賮砜纯?Muitimap 能做些什么 :
HashMultimap<String, String> map = HashMultimap.create(); for(Ticket ticket: tickets){ map.put(ticket.getCandidate(), ticket.getVoter()); }
就這么簡(jiǎn)單!
Muitimap 接口的主要實(shí)現(xiàn)類有:
BiMap 實(shí)現(xiàn)了 java.util.Map 接口。它的特點(diǎn)是它的 value 和它 key 一樣也是不可重復(fù)的,換句話說它的 key 和 value 是等價(jià)的。如果你往 BiMap 的 value 里面放了重復(fù)的元素,就會(huì)得到 IllegalArgumentException。
舉個(gè)例子,你可能經(jīng)常會(huì)碰到在 Map 里面根據(jù) value 值來反推它的 key 值的邏輯:
for(Map.Entry<User, Address> entry : map.entreSet()){ if(entry.getValue().equals(anAddess)){ return entry.getKey(); } } return null;
如果把 User 和 Address 都放在 BiMap,那么一句代碼就得到結(jié)果了:
return biMap.inverse().get(anAddess);
這里的 inverse 方法就是把 BiMap 的 key 集合 value 集合對(duì)調(diào),因此 biMap == biMap.inverse().inverse()。
BiMap的常用實(shí)現(xiàn)有:
HashBiMap: key 集合與 value 集合都有 HashMap 實(shí)現(xiàn)
EnumBiMap: key 與 value 都必須是 enum 類型
ImmutableBiMap: 不可修改的 BiMap
MapMaker 是用來構(gòu)造 ConcurrentMap 的工具類。為什么可以把 MapMaker 叫做超級(jí)強(qiáng)大?看了下面的例子你就知道了。首先,它可以用來構(gòu)造 ConcurrentHashMap:
//ConcurrentHashMap with concurrency level 8 ConcurrentMap<String, Object> map1 = new MapMaker() .concurrencyLevel(8) .makeMap();
或者構(gòu)造用各種不同 reference 作為 key 和 value 的 Map:
//ConcurrentMap with soft reference key and weak reference value ConcurrentMap<String, Object> map2 = new MapMaker() .softKeys() .weakValues() .makeMap();
或者構(gòu)造有自動(dòng)移除時(shí)間過期項(xiàng)的 Map:
//Automatically removed entries from map after 30 seconds since they are created ConcurrentMap<String, Object> map3 = new MapMaker() .expireAfterWrite(30, TimeUnit.SECONDS) .makeMap();
或者構(gòu)造有最大限制數(shù)目的 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();
或者提供當(dāng) Map 里面不包含所 get 的項(xiàng),而需要自動(dòng)加入到 Map 的功能。這個(gè)功能當(dāng) Map 作為緩存的時(shí)候很有用 :
//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); }});
這些還不是最強(qiáng)大的特性,最厲害的是 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); }});
要對(duì)集合排序或者求最大值最小值,首推 java.util.Collections 類,但關(guān)鍵是要提供 Comparator 接口的實(shí)現(xiàn)。假設(shè)有個(gè)待排序的 List<Foo>,而 Foo 里面有兩個(gè)排序關(guān)鍵字 int a, int b 和 int c:
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;
}});
這看上去有點(diǎn)眼暈,如果用一串 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(); }});
如果排序關(guān)鍵字要用自定義比較器,compare 方法也有接受 Comparator 的重載版本。譬如 Foo 里面每個(gè)排序關(guān)鍵字都已經(jīng)有了各自的 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 類還提供了一個(gè)組合 Comparator 對(duì)象的方法。而且 Ordring 本身實(shí)現(xiàn)了 Comparator 接口所以它能直接作為 Comparator 使用:
Ordering<Foo> ordering = Ordering.compound( Arrays.asList(comparatorA, comparatorB, comparatorc)); Collections.sort(list, ordering);
過濾器:利用 Collections2.filter() 方法過濾集合中不符合條件的元素。譬如過濾一個(gè) List<Integer> 里面小于 10 的元素 :
Collection<Integer> filterCollection = Collections2.filter(list, new Predicate<Integer>(){ @Override public boolean apply(Integer input) { return input >= 10; }});
當(dāng)然,你可以自己寫一個(gè)循環(huán)來實(shí)現(xiàn)這個(gè)功能,但是這樣不能保證之后小于 10 的元素不被放入集合。filter 的強(qiáng)大之處在于返回的 filterCollection 仍然有排斥小于 10 的元素的特性,如果調(diào) filterCollection.add(9) 就會(huì)得到一個(gè) IllegalArgumentException。
轉(zhuǎn)換器:利用 Collections2.transform() 方法來轉(zhuǎn)換集合中的元素。譬如把一個(gè) Set<Integer> 里面所有元素都轉(zhuǎn)換成帶格式的 String 來產(chǎn)生新的 Collection<String>:
Collection<String> formatCollection = Collections2.transform(set, new Function<Integer, String>(){ @Override public String apply(Integer input) { return new DecimalFormat("#,###").format(input); }} );
這個(gè)開源項(xiàng)目發(fā)布的 jar 包可以在它的官方網(wǎng)站內(nèi)(http://code.google.com/p/guava-libraries/downloads/list)找到。其下載的 zip 包中含有 Guava Collections 的 jar 包 guava-r09.jar 及其依賴包 guava-r09-gwt.jar,javadoc,源代碼,readme 等文件。使用時(shí)只需將 guava-r09.jar 和依賴包 guava-r09-gwt.jar 放入 CLASSPATH 中即可。
如果您使用 Maven 作為構(gòu)建工具的話可以在 pom.xml 內(nèi)加入:
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>r09</version> </dependency>
需要注意的是本文介紹的 Guava r09 需要 1.5 或者更高版本的 JDK。
聯(lián)系客服