背景
先來回顧一下 JDK 的 Collections , JAVA.util.Collections 提供了一系列靜態方法,能更方便地操作各種集合。
比如:
- 創建空集合 Collections.emptyList();
- 創建單元素集合 Collections.singletonList("Apple");
- 排序 Collections.sort(list);
- 創建不可變集合 Collections.unmodifiableList(mutable);
- 創建線程安全集合 Collections.synchronizedList(list);
- ......
Guava 沿著 Collections 的思路 提供了 更多的工具方法,適用于所有集合的靜態方法,使之成為更強大的集合工具類。
Guava 提供的集合工具不止是對 Collections 的擴展和增強,還包括了其他集合類型的工具類,我們把工具類與特定集合接口的對應關系歸納如下:
Interface 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
靜態構造器
在 JDK 7 之前,構造新的范型集合時要討厭地重復聲明范型:
List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<TypeThatsTooLongForItsOwnGood>();
JDK 7 以后因為有了鉆石操作符(Diamond Operator)可以自動推斷參數類型,所以省點兒事兒
List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<>();
用 Guava 可以這樣寫:
List<TypeThatsTooLongForItsOwnGood> list = Lists.newArrayList();
你可能覺得:這沒什么牛的呀,跟 JDK7 以后沒啥區別呀,人家還是原生的。
是的,沒錯,尤其是你用 JDK 9 以后的版本,JDK 從功能上跟 Guava 就基本一樣了,舉個例子,比如帶元素初始化:
List<String> theseElements = Lists.newArrayList("alpha", "beta", "gamma");
上面這行是利用了 Guava 的 Lists ,JDK 7 沒有比這行代碼更好的方法,但 JDK9 人家有,比如:
List<String> theseElements2 = List.of("alpha", "beta", "gamma");
所以我們說,跟著 Guava 學 Java,隨著版本的迭代,你覺得哪個好用,哪個適合你用哪個,我的重要是把這里面的知識點講清楚。
我們再來看個例子:創建集合時指定初始化集合大小:
List<Type> exactly100 = Lists.newArrayListWithCapacity(100);
你可能說,哥們,這 JDK 有啊,這不多此一舉嗎?
ArrayList<Object> objects = new ArrayList<>(10);
是的,作用一樣,但 Guava 的做法,可以利用工廠方法名稱,提高代碼的可讀性,你不覺得雖然名字長了,但可讀性比 JDK 那個好很多嗎?代碼不止是寫給機器的,也是寫給人看的,不能指望團隊里所有人都是強者。代碼可讀性越高,越健壯越容易維護。
Iterables
Iterables 類包含了一系列的靜態方法,來操作或返回 Iterable 對象
看幾個常用的方法:
方法 描述 concat(Iterable) 串聯多個 iterables 的懶視圖 frequency(Iterable, Object) 返回對象在 iterable 中出現的次數 partition(Iterable, int) 把 iterable 按指定大小分割,得到的子集都不能進行修改操作 getFirst(Iterable, T default) 返回 iterable 的第一個元素,若 iterable 為空則返回默認值 getLast(Iterable) 返回 iterable 的最后一個元素,若 iterable 為空則拋出 NoSuchElementException elementsEqual(Iterable, Iterable) 如果兩個 iterable 中的所有元素相等且順序一致,返回 true unmodifiableIterable(Iterable) 返回 iterable 的不可變視圖 limit(Iterable, int) 最多返回指定數量的元素 getOnlyElement(Iterable) 獲取 iterable 中唯一的元素,如果 iterable 為空或有多個元素,則快速失敗
對于上面這些常用的方法,可能你覺得使用 JDK8 以后的 Stream 一行也都搞定了,是的,還是那句話,Guava 是個工具,尤其在 JDK8 之前用來增強 API 很好用,但工具不止一個,Java 也在發展,有些東西就變成可選項了,看你的需求和習慣使用。Guava 也有對應的流式風格的工具類,比如 FluentIterable
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}}
Sets
Sets工具類包含了若干好用的方法。
Sets 中 為我們提供了很多標準的集合運算(Set-Theoretic)方法。比如我們常用的集合的 “交、并、差集” 以及對稱差集
交集
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<String> wordsWithPrimeLength = ImmutableSet.of("one", "two", "three", "six", "seven", "eight");
Set<String> primes = ImmutableSet.of("two", "three", "five", "seven");
SetView<String> union = Sets.union(primes,wordsWithPrimeLength);
// union 包含 [two, three, five, seven, one, six, eight]
return intersection.immutableCopy();
差集
Set<String> wordsWithPrimeLength = ImmutableSet.of("one", "two", "three", "six", "seven", "eight");
Set<String> primes = ImmutableSet.of("two", "three", "five", "seven");
SetView<String> difference = Sets.union(primes,wordsWithPrimeLength);
// difference 包含 “five”
return difference.immutableCopy();
對稱差集
Set<String> wordsWithPrimeLength = ImmutableSet.of("one", "two", "three", "six", "seven", "eight");
Set<String> primes = ImmutableSet.of("two", "three", "five", "seven");
SetView<String> symmetricDifference = Sets.union(primes,wordsWithPrimeLength);
// symmetricDifference 包含 [five, one, six, eight]
return symmetricDifference.immutableCopy();
注意返回的都是 SetView ,它可以:
- 直接當作 Set 使用,因為 SetView 也實現了 Set 接口
- 用copyInto(Set)拷貝進另一個可變集合
- 用immutableCopy()對自己做不可變拷貝
笛卡兒積
方法 描述 cartesianProduct(List<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"}}
Maps
Maps 有若干很酷的方法。
uniqueIndex
有一組對象,它們在某個屬性上分別有獨一無二的值,而我們希望能夠按照這個屬性值查找對象。
比方說,我們有一堆字符串,這些字符串的長度都是獨一無二的,而我們希望能夠按照特定長度查找字符串:
ImmutableMap<Integer, String> stringsByIndex = Maps.uniqueIndex(strings,
new Function<String, Integer> () {
public Integer apply(String string) {
return string.length();
}
});
你可以想到了,我們常見的場景還有 把一個 List 的對象集合轉成 Map,map 的 key 通常是對象的 ID。用 uniqueIndex 方法可以這樣寫:
Map<Integer, Animal> map = Maps.uniqueIndex(list, Animal::getId);
或者你用 Java8 的 Stream 也一樣:
ArrayList<Animal> animals = Lists.newArrayList(new Animal(1L, "Dog"), new Animal(2L, "Cat"));
//下面兩種寫法都可以
Map<Long, Animal> map = animals.stream().collect(Collectors.toMap(Animal::getId, Function.identity()));
Map<Long, Animal> map = animals.stream().collect(Collectors.toMap(Animal::getId, animal -> animal));
注意:key 要是唯一的,否則會報錯。
difference
找不同,對比兩個 map,告訴你哪里不同
Maps.difference(Map, Map)用來比較兩個 Map 以獲取所有不同點。該方法返回 MapDifference 對象
下面是 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> 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}
看到這個你能想到什么? 我舉個場景:審計日志或操作日志,誰什么時間做了什么,數據從舊值變更為新值,這些要記錄下來
是不是可以用上面這個 Maps 的方法? 適合不適合你自己決定,這里是提供個思路。
MultiSets
“
下面要介紹的工具類都是新集合類型的工具類,比如 MultiSet 和 MultiMap 之類的,有關這些 Guava 的新集合類型,在之前的文章 《跟著 Guava 學 Java 之 新集合類型》 都有介紹,有不清楚的可以再翻回去看一看。
”
標準的 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
index
Multimaps 的 index 方法跟前面介紹的 Maps.uniqueIndex 方法是兄弟方法。與 uniqueIndex 方法相反,通常針對的場景是:有一組對象,它們有共同的特定屬性,我們希望按照這個屬性的值查詢對象,但屬性值不一定是獨一無二的。比方說,我們想把字符串按長度分組:
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 可以把多個鍵映射到同一個值,也可以把一個鍵映射到多個值,反轉 Multimap 也會很有用。Guava 提供了invertFrom(Multimap toInvert, Multimap dest)做這個操作,并且你可以自由選擇反轉后的 Multimap 實現。
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"}]
參考
- https://www.baeldung.com/java-list-to-map
- https://wizardforcel.gitbooks.io/guava-tutorial/content/11.html