任何對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 |
Queue | JDK | Queues |
Multiset | Guava | Multisets |
Multimap | Guava | Multimaps |
BiMap | Guava | Maps |
Table | Guava | Tables |
靜態工廠方法
在JDK 7之前,構造新的范型集合時要討厭地重復聲明范型:
List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<TypeThatsTooLongForItsOwnGood>();
我想我們都認為這很討厭。因此Guava提供了能夠推斷范型的靜態工廠方法:
List<TypeThatsTooLongForItsOwnGood> list = Lists.newArrayList(); Map<KeyType, LongishValueType> map = Maps.newLinkedHashMap();
可以肯定的是,JDK7版本的鑽石操作符(<>)沒有這樣的麻煩:
List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<>();
但Guava的靜態工廠方法遠不止這么簡單。用工廠方法模式,我們可以方便地在初始化時就指定起始元素。
Set<Type> copySet = Sets.newHashSet(elements); List<String> theseElements = Lists.newArrayList("alpha", "beta", "gamma");
此外,通過為工廠方法命名(Effective Java第一條),我們可以提高集合初始化大小的可讀性:
List<Type> exactly100 = Lists.newArrayListWithCapacity(100); List<Type> approx100 = Lists.newArrayListWithExpectedSize(100); Set<Type> approx100Set = Sets.newHashSetWithExpectedSize(100);
確切的靜態工廠方法和相應的工具類一起羅列在下面的章節。
注意:Guava引入的新集合類型沒有暴露原始構造器,也沒有在工具類中提供初始化方法。而是直接在集合類中提供了靜態工廠方法,例如:
Multiset<String> multiset = HashMultiset.create();
Iterables
在可能的情況下,Guava提供的工具方法更偏向於接受Iterable而不是Collection類型。在Google,對於不存放在主存的集合——比如從數據庫或其他數據中心收集的結果集,因為實際上還沒有攫取全部數據,這類結果集都不能支持類似size()的操作 ——通常都不會用Collection類型來表示。
因此,很多你期望的支持所有集合的操作都在Iterables類中。大多數Iterables方法有一個在Iterators類中的對應版本,用來處理Iterator。
截至Guava 1.2版本,Iterables使用FluentIterable類進行了補充,它包裝了一個Iterable實例,並對許多操作提供了”fluent”(鏈式調用)語法。
下面列出了一些最常用的工具方法。
常規方法
concat(Iterable<Iterable>) | 串聯多個iterables的懶視圖* | concat(Iterable...) |
frequency(Iterable, Object) | 返回對象在iterable中出現的次數 | 與Collections.frequency (Collection, Object)比較;Multiset |
partition(Iterable, int) | 把iterable按指定大小分割,得到的子集都不能進行修改操作 | Lists.partition(List, int);paddedPartition(Iterable, int) |
getFirst(Iterable, T default) | 返回iterable的第一個元素,若iterable為空則返回默認值 | 與Iterable.iterator(). next()比較;FluentIterable.first() |
getLast(Iterable) | 返回iterable的最后一個元素,若iterable為空則拋出NoSuchElementException | getLast(Iterable, T default); FluentIterable.last() |
elementsEqual(Iterable, Iterable) | 如果兩個iterable中的所有元素相等且順序一致,返回true | 與List.equals(Object)比較 |
unmodifiableIterable(Iterable) | 返回iterable的不可變視圖 | 與Collections. unmodifiableCollection(Collection)比較 |
limit(Iterable, int) | 限制iterable的元素個數限制給定值 | FluentIterable.limit(int) |
getOnlyElement(Iterable) | 獲取iterable中唯一的元素,如果iterable為空或有多個元素,則快速失敗 | getOnlyElement(Iterable, T default) |
*譯者注:懶視圖意味着如果還沒訪問到某個iterable中的元素,則不會對它進行串聯操作。
Iterable<Integer> concatenated = Iterables.concat( Ints.asList(1, 2, 3), Ints.asList(4, 5, 6)); // concatenated包括元素 1, 2, 3, 4, 5, 6 String lastAdded = Iterables.getLast(myLinkedHashSet); String theElement = Iterables.getOnlyElement(thisSetIsDefinitelyASingleton); //如果set不是單元素集,就會出錯了!
與Collection方法相似的工具方法
通常來說,Collection的實現天然支持操作其他Collection,但卻不能操作Iterable。
下面的方法中,如果傳入的Iterable是一個Collection實例,則實際操作將會委托給相應的Collection接口方法。例如,往Iterables.size方法傳入是一個Collection實例,它不會真的遍歷iterator獲取大小,而是直接調用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() |
FluentIterable
除了上面和第四章提到的方法,FluentIterable還有一些便利方法用來把自己拷貝到不可變集合
ImmutableList | |
ImmutableSet | toImmutableSet() |
ImmutableSortedSet | toImmutableSortedSet(Comparator) |
Lists
除了靜態工廠方法和函數式編程方法,Lists為List類型的對象提供了若干工具方法。
方法 | 描述 |
partition(List, int) | 把List按指定大小分割 |
reverse(List) | 返回給定List的反轉視圖。注: 如果List是不可變的,考慮改用ImmutableList.reverse()。 |
List countUp = Ints.asList(1, 2, 3, 4, 5); List countDown = Lists.reverse(theList); // {5, 4, 3, 2, 1} List<List> parts = Lists.partition(countUp, 2);//{{1,2}, {3,4}, {5}}
靜態工廠方法
Lists提供如下靜態工廠方法:
具體實現類型 | 工廠方法 |
ArrayList | basic, with elements, from Iterable, with exact capacity, with expected size, from Iterator |
LinkedList | basic, from Iterable |
Sets
Sets工具類包含了若干好用的方法。
集合理論方法
我們提供了很多標准的集合運算(Set-Theoretic)方法,這些方法接受Set參數並返回SetView,可用於:
- 直接當作Set使用,因為SetView也實現了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); // intersection包含"two", "three", "seven" return intersection.immutableCopy();//可以使用交集,但不可變拷貝的讀取效率更高
其他Set工具方法
方法 | 描述 | 另請參見 |
cartesianProduct(List<Set>) | 返回所有集合的笛卡兒積 | 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"}}
靜態工廠方法
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 |
Maps
Maps類有若干值得單獨說明的、很酷的方法。
uniqueIndex
Maps.uniqueIndex(Iterable,Function)通常針對的場景是:有一組對象,它們在某個屬性上分別有獨一無二的值,而我們希望能夠按照這個屬性值查找對象——譯者注:這個方法返回一個Map,鍵為Function返回的屬性值,值為Iterable中相應的元素,因此我們可以反復用這個Map進行查找操作。
比方說,我們有一堆字符串,這些字符串的長度都是獨一無二的,而我們希望能夠按照特定長度查找字符串:
ImmutableMap<Integer, String> stringsByIndex = Maps.uniqueIndex(strings, new Function<String, Integer> () { public Integer apply(String string) { return string.length(); } });
如果索引值不是獨一無二的,請參見下面的Multimaps.index方法。
difference
Maps.difference(Map, Map)用來比較兩個Map以獲取所有不同點。該方法返回MapDifference對象,把不同點的維恩圖分解為:
entriesInCommon() | 兩個Map中都有的映射項,包括匹配的鍵與值 |
entriesDiffering() | 鍵相同但是值不同值映射項。返回的Map的值類型為MapDifference.ValueDifference,以表示左右兩個不同的值 |
entriesOnlyOnLeft() | 鍵只存在於左邊Map的映射項 |
entriesOnlyOnRight() | 鍵只存在於右邊Map的映射項 |
Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3); Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3); MapDifference<String, Integer> diff = Maps.difference(left, right); diff.entriesInCommon(); // {"b" => 2} diff.entriesInCommon(); // {"b" => 2} diff.entriesOnlyOnLeft(); // {"a" => 1} diff.entriesOnlyOnRight(); // {"d" => 5}
處理BiMap的工具方法
Guava中處理BiMap的工具方法在Maps類中,因為BiMap也是一種Map實現。
BiMap工具方法 | 相應的Map工具方法 |
synchronizedBiMap(BiMap) | Collections.synchronizedMap(Map) |
unmodifiableBiMap(BiMap) | Collections.unmodifiableMap(Map) |
靜態工廠方法
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 |
Multisets
標准的Collection操作會忽略Multiset重復元素的個數,而只關心元素是否存在於Multiset中,如containsAll方法。為此,Multisets提供了若干方法,以顧及Multiset元素的重復性:
方法 | 說明 | 和Collection方法的區別 |
containsOccurrences(Multiset sup, Multiset sub) | 對任意o,如果sub.count(o)<=super.count(o),返回true | Collection.containsAll忽略個數,而只關心sub的元素是否都在super中 |
removeOccurrences(Multiset removeFrom, Multiset toRemove) | 對toRemove中的重復元素,僅在removeFrom中刪除相同個數。 | Collection.removeAll移除所有出現在toRemove的元素 |
retainOccurrences(Multiset removeFrom, Multiset toRetain) | 修改removeFrom,以保證任意o都符合removeFrom.count(o)<=toRetain.count(o) | Collection.retainAll保留所有出現在toRetain的元素 |
intersection(Multiset, Multiset) | 返回兩個multiset的交集; | 沒有類似方法 |
Multiset<String> multiset1 = HashMultiset.create(); multiset1.add("a", 2); Multiset<String> multiset2 = HashMultiset.create(); multiset2.add("a", 5); multiset1.containsAll(multiset2); //返回true;因為包含了所有不重復元素, //雖然multiset1實際上包含2個"a",而multiset2包含5個"a" Multisets.containsOccurrences(multiset1, multiset2); // returns false multiset2.removeOccurrences(multiset1); // multiset2 現在包含3個"a" multiset2.removeAll(multiset1);//multiset2移除所有"a",雖然multiset1只有2個"a" multiset2.isEmpty(); // returns true
Multisets中的其他工具方法還包括:
copyHighestCountFirst(Multiset) | 返回Multiset的不可變拷貝,並將元素按重復出現的次數做降序排列 |
unmodifiableMultiset(Multiset) | 返回Multiset的只讀視圖 |
unmodifiableSortedMultiset(SortedMultiset) | 返回SortedMultiset的只讀視圖 |
Multiset<String> multiset = HashMultiset.create(); multiset.add("a", 3); multiset.add("b", 5); multiset.add("c", 1); ImmutableMultiset highestCountFirst = Multisets.copyHighestCountFirst(multiset); //highestCountFirst,包括它的entrySet和elementSet,按{"b", "a", "c"}排列元素
Multimaps
Multimaps提供了若干值得單獨說明的通用工具方法
index
作為Maps.uniqueIndex的兄弟方法,Multimaps.index(Iterable, Function)通常針對的場景是:有一組對象,它們有共同的特定屬性,我們希望按照這個屬性的值查詢對象,但屬性值不一定是獨一無二的。
比方說,我們想把字符串按長度分組。
ImmutableSet 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"} */
invertFrom
鑒於Multimap可以把多個鍵映射到同一個值(譯者注:實際上這是任何map都有的特性),也可以把一個鍵映射到多個值,反轉Multimap也會很有用。Guava 提供了invertFrom(Multimap toInvert,
Multimap dest)做這個操作,並且你可以自由選擇反轉后的Multimap實現。
注:如果你使用的是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()); //注意我們選擇的實現,因為選了TreeMultimap,得到的反轉結果是有序的 /* * inverse maps: * 1 => {"a"} * 2 => {"a", "b", "c"} * 3 => {"c"} * 4 => {"a", "b"} * 5 => {"c"} * 6 => {"b"} */
forMap
想在Map對象上使用Multimap的方法嗎?forMap(Map)把Map包裝成SetMultimap。這個方法特別有用,例如,與Multimaps.invertFrom結合使用,可以把多對一的Map反轉為一對多的Multimap。
Map<String, Integer> map = ImmutableMap.of("a", 1, "b", 1, "c", 2); SetMultimap<String, Integer> multimap = Multimaps.forMap(map); // multimap:["a" => {1}, "b" => {1}, "c" => {2}] Multimap<Integer, String> inverse = Multimaps.invertFrom(multimap, HashMultimap<Integer, String>.create()); // inverse:[1 => {"a","b"}, 2 => {"c"}]
包裝器
Multimaps提供了傳統的包裝方法,以及讓你選擇Map和Collection類型以自定義Multimap實現的工具方法。
只讀包裝 | Multimap | ListMultimap | SetMultimap | SortedSetMultimap |
同步包裝 | Multimap | ListMultimap | SetMultimap | SortedSetMultimap |
自定義實現 | Multimap | ListMultimap | SetMultimap | SortedSetMultimap |
自定義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存儲。
ListMultimap<String, Integer> myMultimap = Multimaps.newListMultimap( Maps.<String, Collection>newTreeMap(), new Supplier<LinkedList>() { public LinkedList get() { return Lists.newLinkedList(); } });
Tables
Tables類提供了若干稱手的工具方法。
自定義Table
堪比Multimaps.newXXXMultimap(Map, Supplier)工具方法,Tables.newCustomTable(Map, Supplier<Map>)允許你指定Table用什么樣的map實現行和列。
// 使用LinkedHashMaps替代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(); } });
transpose
transpose(Table<R, C, V>)方法允許你把Table<C, R, V>轉置成Table<R, C, V>。例如,如果你在用Table構建加權有向圖,這個方法就可以把有向圖反轉。
包裝器
還有很多你熟悉和喜歡的Table包裝類。然而,在大多數情況下還請使用ImmutableTable