[Google Guava]--java.util.Collections中未包含的集合工具(com.google.common.collect)
任何對JDK集合框架有經驗的程式設計師都熟悉和喜歡java.util.Collections包含的工具方法。Guava沿著這些路線提供了更多的工具方法:適用於所有集合的靜態方法。這是Guava最流行和成熟的部分之一。
我們用相對直觀的方式把工具類與特定集合介面的對應關係歸納如下:
集合介面 | 屬於JDK還是Guava | 對應的Guava工具類 |
Collection | JDK | Collections2:不要和java.util.Collections混淆 |
List | JDK | Lists |
Set | JDK | Sets |
SortedSet | JDK | Sets |
Map | JDK | Maps |
SortedMap | JDK | Maps |
BiMap | Guava | Maps |
Table | Guava |
在找類似轉化、過濾的方法?請看第四章,函式式風格。
靜態工廠方法
在JDK 7之前,構造新的範型集合時要討厭地重複聲明範型:
1 | List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<TypeThatsTooLongForItsOwnGood>(); |
我想我們都認為這很討厭。因此Guava提供了能夠推斷範型的靜態工廠方法:
1 | List<TypeThatsTooLongForItsOwnGood> list = Lists.newArrayList(); |
2 | Map<KeyType, LongishValueType> map = Maps.newLinkedHashMap(); |
可以肯定的是,JDK7版本的鑽石操作符(<>)沒有這樣的麻煩:
1 | List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<>(); |
但Guava的靜態工廠方法遠不止這麼簡單。用工廠方法模式,我們可以方便地在初始化時就指定起始元素。
1 | Set<Type> copySet = Sets.newHashSet(elements); |
2 | List<String> theseElements = Lists.newArrayList( "alpha" , "beta" , "gamma" ); |
此外,通過為工廠方法命名(Effective Java第一條),我們可以提高集合初始化大小的可讀性:
1 | List<Type> exactly100 = Lists.newArrayListWithCapacity( 100 ); |
2 | List<Type> approx100 = Lists.newArrayListWithExpectedSize( 100 ); |
3 | Set<Type> approx100Set = Sets.newHashSetWithExpectedSize( 100 ); |
確切的靜態工廠方法和相應的工具類一起羅列在下面的章節。
注意:Guava引入的新集合型別沒有暴露原始構造器,也沒有在工具類中提供初始化方法。而是直接在集合類中提供了靜態工廠方法,例如:
1 | Multiset<String> multiset = HashMultiset.create(); |
Iterables
在可能的情況下,Guava提供的工具方法更偏向於接受Iterable而不是Collection型別。在Google,對於不存放在主存的集合——比如從資料庫或其他資料中心收集的結果集,因為實際上還沒有攫取全部資料,這類結果集都不能支援類似size()的操作 ——通常都不會用Collection型別來表示。
因此,很多你期望的支援所有集合的操作都在Iterables類中。大多數Iterables方法有一個在Iterators類中的對應版本,用來處理Iterator。
截至Guava 1.2版本,Iterables使用進行了補充,它包裝了一個Iterable例項,並對許多操作提供了”fluent”(鏈式呼叫)語法。
下面列出了一些最常用的工具方法,但更多Iterables的函式式方法將在第四章討論。
常規方法
*譯者注:懶檢視意味著如果還沒訪問到某個iterable中的元素,則不會對它進行串聯操作。
1 | Iterable<Integer> concatenated = Iterables.concat( |
2 | Ints.asList( 1 , 2 , 3 ), |
3 | Ints.asList( 4 , 5 , 6 )); // concatenated包括元素 1, 2, 3, 4, 5, 6 |
4 | String lastAdded = Iterables.getLast(myLinkedHashSet); |
5 | String theElement = Iterables.getOnlyElement(thisSetIsDefinitelyASingleton); |
6 | //如果set不是單元素集,就會出錯了! |
與Collection方法相似的工具方法
通常來說,Collection的實現天然支援操作其他Collection,但卻不能操作Iterable。
下面的方法中,如果傳入的Iterable是一個Collection例項,則實際操作將會委託給相應的Collection介面方法。例如,往Iterables.size方法傳入是一個Collection例項,它不會真的遍歷iterator獲取大小,而是直接呼叫Collection.size。
FluentIterable
除了上面和第四章提到的方法,FluentIterable還有一些便利方法用來把自己拷貝到不可變集合
Lists
除了靜態工廠方法和函數語言程式設計方法,Lists為List型別的物件提供了若干工具方法。
1 | List countUp = Ints.asList( 1 , 2 , 3 , 4 , 5 ); |
2 | List countDown = Lists.reverse(theList); // {5, 4, 3, 2, 1} |
3 | List<List> parts = Lists.partition(countUp, 2 ); //{{1,2}, {3,4}, {5}} |
靜態工廠方法
Lists提供如下靜態工廠方法:
Sets
Sets工具類包含了若干好用的方法。
集合理論方法
我們提供了很多標準的集合運算(Set-Theoretic)方法,這些方法接受Set引數並返回SetView,可用於:
- 直接當作Set使用,因為SetView也實現了Set介面;
使用範例:
1 | Set<String> wordsWithPrimeLength = ImmutableSet.of( "one" , "two" , "three" , "six" , "seven" , "eight" ); |
2 | Set<String> primes = ImmutableSet.of( "two" , "three" , "five" , "seven" ); |
3 | SetView<String> intersection = Sets.intersection(primes,wordsWithPrimeLength); |
4 | // intersection包含"two", "three", "seven" |
5 | return intersection.immutableCopy(); //可以使用交集,但不可變拷貝的讀取效率更高 |
其他Set工具方法
1 | Set<String> animals = ImmutableSet.of( "gerbil" , "hamster" ); |
2 | Set<String> fruits = ImmutableSet.of( "apple" , "orange" , "banana" ); |
3 |
4 | Set<List<String>> product = Sets.cartesianProduct(animals, fruits); |
5 | // {{"gerbil", "apple"}, {"gerbil", "orange"}, {"gerbil", "banana"}, |
6 | // {"hamster", "apple"}, {"hamster", "orange"}, {"hamster", "banana"}} |
7 |
8 | Set<Set<String>> animalSets = Sets.powerSet(animals); |
9 | // {{}, {"gerbil"}, {"hamster"}, {"gerbil", "hamster"}} |
靜態工廠方法
Sets提供如下靜態工廠方法:
Maps
Maps類有若干值得單獨說明的、很酷的方法。
uniqueIndex
Maps.uniqueIndex(Iterable,Function)通常針對的場景是:有一組物件,它們在某個屬性上分別有獨一無二的值,而我們希望能夠按照這個屬性值查詢物件——譯者注:這個方法返回一個Map,鍵為Function返回的屬性值,值為Iterable中相應的元素,因此我們可以反覆用這個Map進行查詢操作。
比方說,我們有一堆字串,這些字串的長度都是獨一無二的,而我們希望能夠按照特定長度查詢字串:
1 | ImmutableMap<Integer, String> stringsByIndex = Maps.uniqueIndex(strings, |
2 | new Function<String, Integer> () { |
3 | public Integer apply(String string) { |
4 | return string.length(); |
5 | } |
6 | }); |
如果索引值不是獨一無二的,請參見下面的Multimaps.index方法。
difference
Maps.difference(Map, Map)用來比較兩個Map以獲取所有不同點。該方法返回MapDifference物件,把不同點的維恩圖分解為:
1 | Map<String, Integer> left = ImmutableMap.of( "a" , 1 , "b" , 2 , "c" , 3 ); |
2 | Map<String, Integer> left = ImmutableMap.of( "a" , 1 , "b" , 2 , "c" , 3 ); |
3 | MapDifference<String, Integer> diff = Maps.difference(left, right); |
4 |
5 | diff.entriesInCommon(); // {"b" => 2} |
6 | diff.entriesInCommon(); // {"b" => 2} |
7 | diff.entriesOnlyOnLeft(); // {"a" => 1} |
8 | diff.entriesOnlyOnRight(); // {"d" => 5} |
處理BiMap的工具方法
Guava中處理BiMap的工具方法在Maps類中,因為BiMap也是一種Map實現。
BiMap工具方法 | 相應的Map工具方法 |
靜態工廠方法
Maps提供如下靜態工廠方法:
具體實現型別 | 工廠方法 |
LinkedHashMap | |
ConcurrentMap:支援所有操作 | basic |
IdentityHashMap | basic |
Multisets
標準的Collection操作會忽略Multiset重複元素的個數,而只關心元素是否存在於Multiset中,如containsAll方法。為此,Multisets提供了若干方法,以顧及Multiset元素的重複性:
方法 | 說明 | 和Collection方法的區別 |
對任意o,如果sub.count(o)<=super.count(o),返回true | Collection.containsAll忽略個數,而只關心sub的元素是否都在super中 | |
修改removeFrom,以保證任意o都符合removeFrom.count(o)<=toRetain.count(o) | Collection.retainAll保留所有出現在toRetain的元素 |
01 | Multiset<String> multiset1 = HashMultiset.create(); |
02 | multiset1.add( "a" , 2 ); |
03 |
04 | Multiset<String> multiset2 = HashMultiset.create(); |
05 | multiset2.add( "a" , 5 ); |
06 |
07 | multiset1.containsAll(multiset2); //返回true;因為包含了所有不重複元素, |
08 | //雖然multiset1實際上包含2個"a",而multiset2包含5個"a" |
09 | Multisets.containsOccurrences(multiset1, multiset2); // returns false |
10 |
11 | multiset2.removeOccurrences(multiset1); // multiset2 現在包含3個"a" |
12 | multiset2.removeAll(multiset1); //multiset2移除所有"a",雖然multiset1只有2個"a" |
13 | multiset2.isEmpty(); // returns true |
Multisets中的其他工具方法還包括:
1 | Multiset<String> multiset = HashMultiset.create(); |
2 | multiset.add( "a" , 3 ); |
3 | multiset.add( "b" , 5 ); |
4 | multiset.add( "c" , 1 ); |
5 |
6 | ImmutableMultiset highestCountFirst = Multisets.copyHighestCountFirst(multiset); |
7 | //highestCountFirst,包括它的entrySet和elementSet,按{"b", "a", "c"}排列元素 |
Multimaps
Multimaps提供了若干值得單獨說明的通用工具方法
index
作為Maps.uniqueIndex的兄弟方法,Multimaps.index(Iterable, Function)通常針對的場景是:有一組物件,它們有共同的特定屬性,我們希望按照這個屬性的值查詢物件,但屬性值不一定是獨一無二的。
比方說,我們想把字串按長度分組。
01 | ImmutableSet digits = ImmutableSet.of( "zero" , "one" , "two" , "three" , "four" , "five" , "six" , "seven" , "eight" , "nine" ); |
02 | Function<String, Integer> lengthFunction = new Function<String, Integer>() { |
03 | public Integer apply(String string) { |
04 | return string.length(); |
05 | } |
06 | }; |
07 |
08 | ImmutableListMultimap<Integer, String> digitsByLength= Multimaps.index(digits, lengthFunction); |
09 | /* |
10 | * digitsByLength maps: |
11 | * 3 => {"one", "two", "six"} |
12 | * 4 => {"zero", "four", "five", "nine"} |
13 | * 5 => {"three", "seven", "eight"} |
14 | */ |
invertFrom
鑑於Multimap可以把多個鍵對映到同一個值(譯者注:實際上這是任何map都有的特性),也可以把一個鍵對映到多個值,反轉Multimap也會很有用。Guava 提供了invertFrom(Multimap toInvert,
Multimap dest)做這個操作,並且你可以自由選擇反轉後的Multimap實現。
01 | ArrayListMultimap<String, Integer> multimap = ArrayListMultimap.create(); |
02 | multimap.putAll( "b" , Ints.asList( 2 , 4 , 6 )); |
03 | multimap.putAll( "a" , Ints.asList( 4 , 2 , 1 )); |
04 | multimap.putAll( "c" , Ints.asList( 2 , 5 , 3 )); |
05 |
06 | TreeMultimap<Integer, String> inverse = Multimaps.invertFrom(multimap, TreeMultimap<String, Integer>.create()); |
07 | //注意我們選擇的實現,因為選了TreeMultimap,得到的反轉結果是有序的 |
08 | /* |
09 | * inverse maps: |
10 | * 1 => {"a"} |
11 | * 2 => {"a", "b", "c"} |
12 | * 3 => {"c"} |
13 | * 4 => {"a", "b"} |
14 | * 5 => {"c"} |
15 | * 6 => {"b"} |
16 | */ |
forMap
想在Map物件上使用Multimap的方法嗎?forMap(Map)把Map包裝成SetMultimap。這個方法特別有用,例如,與Multimaps.invertFrom結合使用,可以把多對一的Map反轉為一對多的Multimap。
1 | Map<String, Integer> map = ImmutableMap.of( "a" , 1 , "b" , 1 , "c" , 2 ); |
2 | SetMultimap<String, Integer> multimap = Multimaps.forMap(map); |
3 | // multimap:["a" => {1}, "b" => {1}, "c" => {2}] |
4 | Multimap<Integer, String> inverse = Multimaps.invertFrom(multimap, HashMultimap<Integer, String>.create()); |
5 | // inverse:[1 => {"a","b"}, 2 => {"c"}] |
包裝器
Multimaps提供了傳統的包裝方法,以及讓你選擇Map和Collection型別以自定義Multimap實現的工具方法。
自定義Multimap的方法允許你指定Multimap中的特定實現。但要注意的是:
- Multimap假設對Map和Supplier產生的集合物件有完全所有權。這些自定義物件應避免手動更新,並且在提供給Multimap時應該是空的,此外還不應該使用軟引用、弱引用或虛引用。
- 無法保證修改了Multimap以後,底層Map的內容是什麼樣的。
- 即使Map和Supplier產生的集合都是執行緒安全的,它們組成的Multimap也不能保證併發操作的執行緒安全性。併發讀操作是工作正常的,但需要保證併發讀寫的話,請考慮用同步包裝器解決。
- 只有當Map、Supplier、Supplier產生的集合物件、以及Multimap存放的鍵值型別都是可序列化的,Multimap才是可序列化的。
- Multimap.get(key)返回的集合物件和Supplier返回的集合物件並不是同一型別。但如果Supplier返回的是隨機訪問集合,那麼Multimap.get(key)返回的集合也是可隨機訪問的。
請注意,用來自定義Multimap的方法需要一個Supplier引數,以建立嶄新的集合。下面有個實現ListMultimap的例子——用TreeMap做對映,而每個鍵對應的多個值用LinkedList儲存。
1 | ListMultimap<String, Integer> myMultimap = Multimaps.newListMultimap( |
2 | Maps.<String, Collection>newTreeMap(), |
3 | new Supplier<LinkedList>() { |
4 | public LinkedList get() { |
5 | return Lists.newLinkedList(); |
6 | } |
7 | }); |
Tables
Tables類提供了若干稱手的工具方法。
自定義Table
堪比Multimaps.newXXXMultimap(Map, Supplier)工具方法,Tables.newCustomTable(Map, Supplier<Map>)允許你指定Table用什麼樣的map實現行和列。
1 | // 使用LinkedHashMaps替代HashMaps |
2 | Table<String, Character, Integer> table = Tables.newCustomTable( |
3 | Maps.<String, Map<Character, Integer>>newLinkedHashMap(), |
4 | new Supplier<Map<Character, Integer>> () { |
5 | public Map<Character, Integer> get() { |
6 | return Maps.newLinkedHashMap(); |
7 | } |
8 | }); |
transpose
transpose(Table<R, C, V>)方法允許你把Table<C, R, V>轉置成Table<R, C, V>。例如,如果你在用Table構建加權有向圖,這個方法就可以把有向圖反轉。
包裝器
還有很多你熟悉和喜歡的Table包裝類。然而,在大多數情況下還請使用ImmutableTable