天天看點

Guava學習筆記 2. 集合 [Collections]一、不可變集合二、新集合類型三、集合工具類四、集合擴充工具類

Guava 對 JDK 集合的擴充,這是 Guava 最成熟和為人所知的部分

本文主要通過例子的方式簡單介紹了一下集合的使用以及需要注意的一些細節。

如果希望了解更多的細節,可以可以檢視目錄中的連結進行檢視。

— By Syahfozy

1 不可變集合: 用不變的集合進行防禦性程式設計和性能提升。

2 新集合類型: multisets, multimaps, tables, bidirectional maps 等

3 強大的集合工具類: 提供 java.util.Collections 中沒有的集合工具

4 擴充工具類:讓實作和擴充集合類變得更容易,比如建立 Collection 的裝飾器,或實作疊代器

一、不可變集合

重要提示:所有Guava不可變集合的實作都不接受null值。我們對Google内部的代碼庫做過詳細研究,發現隻有5%的情況需要在集合中允許null元素,剩下的95%場景都是遇到null值就快速失敗。如果你需要在不可變集合中使用null,請使用JDK中的Collections.unmodifiableXXX方法。更多細節建議請參考“使用和避免null”。

1. 建立方法

對有序不可變集合來說,排序是在構造集合的時候完成的

Set<String> colors = Sets.newHashSet("red", "orange", "yellow");
Set<Color> colorSet = Sets.newHashSet(new Color(0, 0, 0));

// copyOf方法
ImmutableSet.copyOf(colors);

// of方法
ImmutableSet.of(colors);
ImmutableSet.of("red", "orange", "yellow");
ImmutableMap.of("a", 1, "b", 2);

// Builder工具
ImmutableSet<Color> GOOGLE_COLORS = ImmutableSet.<Color>builder()
        .addAll(colorSet)
        .add(new Color(0, 191, 255))
        .build();
           

2. 使用方法

ImmutableSet<String> colors = ImmutableSet.of("red", "orange", "yellow");

// ImmutableXXX.copyOf方法會嘗試在安全的時候避免做拷貝
// 在這段代碼中,ImmutableList.copyOf(foobar)會智能地直接傳回foobar.asList()
// 它是一個ImmutableSet的常量時間複雜度的List視圖
ImmutableList.copyOf(colors);

// 所有不可變集合都有一個asList()方法提供ImmutableList視圖
ImmutableList immutableList = colors.asList();
           

細節:關聯可變集合和不可變集合

可變集合接口 屬于 JDK 還是 Guava 不可變版本
Collection JDK ImmutableCollection
List JDK ImmutableList
Set JDK ImmutableSet
SortedSet/NavigableSet JDK ImmutableSortedSet
Map JDK ImmutableMap
SortedMap JDK ImmutableSortedMap
Multiset Guava ImmutableMultiset
SortedMultiset Guava ImmutableSortedMultiset
Multimap Guava ImmutableMultimap
ListMultimap Guava ImmutableListMultimap
SetMultimap Guava ImmutableSetMultimap
BiMap Guava ImmutableBiMap
ClassToInstanceMap Guava ImmutableClassToInstanceMap
Table Guava ImmutableTable

二、新集合類型

Guava引入了很多JDK沒有的、但我們發現明顯有用的新集合類型。這些新類型是為了和JDK集合架構共存,而沒有往JDK集合抽象中硬塞其他概念。作為一般規則,Guava集合非常精準地遵循了JDK接口契約

1. Multiset

  • 可以多次添加相等的元素的集合
  • 可以了解為沒有元素順序限制的ArrayList,Map<E, Integer>,鍵為元素,值為計數

Guava提供了一個新集合類型 Multiset,它可以多次添加相等的元素。

/**
 * 當把Multiset看成普通的Collection時,它表現得就像無序的ArrayList
 */
// add(E)添加單個給定元素
System.out.println(hashMultiset.add("a"));
// 減少給定元素在Multiset中的計數
System.out.println(hashMultiset.remove("a"));
// iterator()傳回一個疊代器,包含Multiset的所有元素(包括重複的元素)
System.out.println(hashMultiset.iterator().next());
// size()傳回所有元素的總個數(包括重複的元素)
System.out.println(hashMultiset.size());


/**
 * 當把Multiset看作Map<E, Integer>時,它也提供了符合性能期望的查詢操作
 */
// count(Object)傳回給定元素的計數
// HashMultiset.count的複雜度為O(1),TreeMultiset.count的複雜度為O(log n)
System.out.println(hashMultiset.count("a"));
// 設定給定元素在Multiset中的計數,不可以為負數
System.out.println(hashMultiset.setCount("c", 2));
// 和Map的entrySet類似,傳回Set<Multiset.Entry<E>>,其中包含的Entry支援getElement()和getCount()方法
System.out.println(hashMultiset.entrySet());
// 傳回所有不重複元素的Set<E>,和Map的keySet()類似
System.out.println(hashMultiset.elementSet());
           

Guava提供了多種Multiset的實作,如

Map 對應的Multiset 是否支援null元素
HashMap HashMultiset
TreeMap TreeMultiset 是(如果comparator支援的話)
LinkedHashMap LinkedHashMultiset
ConcurrentHashMap ConcurrentHashMultiset
ImmutableMap ImmutableMultiset

2. Multimap

Multimap可以把鍵映射到任意多個值

// 總是傳回非null、但是可能空的集合
System.out.println(multimap.get("c"));
// 像Map一樣,沒有的鍵傳回null
System.out.println(multimap.asMap());
// 且僅當有值映射到鍵時,Multimap.containsKey(key)才會傳回true
multimap.remove("b", "ss");
System.out.println(multimap.containsKey("b"));
// 傳回Multimap中所有”鍵-單個值映射”——包括重複鍵
System.out.println(multimap.entries());
// 得到所有”鍵-值集合映射”
System.out.println(multimap.asMap().entrySet());
// 傳回所有”鍵-單個值映射”的個數
System.out.println(multimap.size());
// 不同鍵的個數
System.out.println(multimap.asMap().size());
           

Multimap 提供了多種形式的實作。在大多數要使用 Map<K, Collection> 的地方,你都可以使用它們,即把鍵映射到任意多個值的一般方式:

實作 鍵行為類似 值行為類似
ArrayListMultimap HashMap ArrayList
HashMultimap HashMap HashSet
LinkedListMultimap* LinkedHashMap* LinkedList*
LinkedHashMultimap** LinkedHashMap LinkedHashMap
TreeMultimap TreeMap TreeSet
ImmutableListMultimap ImmutableMap ImmutableList
ImmutableSetMultimap ImmutableMap ImmutableSet

3. BiMap

/**
 * 實作鍵值對的雙向映射的map
 * 可以用 inverse()反轉BiMap<K, V>的鍵值映射
 * 保證值是唯一的,是以 values()傳回Set而不是普通的Collection
 *
 * 鍵–值實作	        值–鍵實作	        對應的BiMap實作
 * HashMap	        HashMap	        HashBiMap
 * ImmutableMap	    ImmutableMap	ImmutableBiMap
 * EnumMap	        EnumMap	        EnumBiMap
 * EnumMap	        HashMap	        EnumHashBiMap
 */
public static void biMap() {

    BiMap<String, Integer> userId = HashBiMap.create();
    userId.put("a", 123);

    // 把鍵映射到已經存在的值,會抛出IllegalArgumentException異常
    // userId.put("b", 123);

    // 強制替換它的鍵
    userId.forcePut("b", 123);
    System.out.println(userId);

    // 反轉BiMap<K, V>的鍵值映射
    System.out.println(userId.inverse().get(123));

    // 保證值是唯一的,是以 values()傳回Set而不是普通的Collection
    Set<Integer> IdSet = userId.values();
    System.out.println(IdSet);

}
    
           

BiMap的各種實作

鍵–值實作 值–鍵實作 對應的BiMap實作
HashMap HashMap HashBiMap
ImmutableMap ImmutableMap ImmutableBiMap
EnumMap EnumMap EnumBiMap
EnumMap HashMap EnumHashBiMap

4. Table

/**
 * 帶有兩個支援所有類型的鍵:”行”和”列”
 * Table有如下幾種實作:
 * HashBasedTable:本質上用HashMap<R, HashMap<C, V>>實作;
 * TreeBasedTable:本質上用TreeMap<R, TreeMap<C,V>>實作;
 * ImmutableTable:本質上用ImmutableMap<R, ImmutableMap<C, V>>實作;注:ImmutableTable對稀疏或密集的資料集都有優化。
 * ArrayTable:要求在構造時就指定行和列的大小,本質上由一個二維數組實作,以提升通路速度和密集Table的記憶體使用率
 */
public static void table() {
    Table<Integer, Integer, Double> weightedGraph = HashBasedTable.create();
    weightedGraph.put(1, 2, 4d);
    weightedGraph.put(1, 3, 20d);
    weightedGraph.put(2, 3, 5d);
    System.out.println(weightedGraph);

    // 用Map<R, Map<C, V>>表現Table<R, C, V>
    Map<Integer, Map<Integer, Double>> map = weightedGraph.rowMap();
    System.out.println(map);

    // 傳回”行”的集合Set<R>
    Set<Integer> set = weightedGraph.rowKeySet();
    System.out.println(set);

    // 用Map<C, V>傳回給定”行”的所有列
    // 類似的列通路方法:columnMap()、columnKeySet()、column(c)
    // (基于列的通路會比基于的行通路稍微低效點)
    Map<Integer, Double> row = weightedGraph.row(2);
    System.out.println(set);
    // 對這個map進行的寫操作也将寫入Table中
    row.put(4, 6d);
    row.put(5, 8d);
    System.out.println(weightedGraph);

    // 用元素類型為Table.Cell<R, C, V>的Set表現Table<R, C, V>
    // Cell類似于Map.Entry,但它是用行和列兩個鍵區分的
    Set<Table.Cell<Integer, Integer, Double>> cells = weightedGraph.cellSet();
    System.out.println(cells);

}
           

5. ClassToInstanceMap

ClassToInstanceMap是一種特殊的Map:它的鍵是類型,而值是符合鍵所指類型的對象

對于ClassToInstanceMap,Guava提供了兩種有用的實作:MutableClassToInstanceMap和 ImmutableClassToInstanceMap

public static void classToInstanceMap() {

    ClassToInstanceMap<Number> numberDefaults=MutableClassToInstanceMap.create();
    numberDefaults.putInstance(Integer.class, Integer.valueOf(0));

    Integer instance =  numberDefaults.getInstance(Integer.class);
    System.out.println(instance);

}
           

6. RangeSet

RangeSet描述了一組不相連的、非空的區間

當把一個區間添加到可變的RangeSet時,所有相連的區間會被合并,空區間會被忽略

public static void rangeSet() {
    RangeSet<Integer> rangeSet = TreeRangeSet.create();

    rangeSet.add(Range.closed(1, 10)); // {[1,10]}
    System.out.println(rangeSet.toString());

    rangeSet.add(Range.closedOpen(11, 15));//不相連區間:{[1,10], [11,15)}
    System.out.println(rangeSet.toString());

    rangeSet.add(Range.closedOpen(15, 20)); //相連區間; {[1,10], [11,20)}
    System.out.println(rangeSet.toString());

    rangeSet.add(Range.openClosed(0, 0)); //空區間; {[1,10], [11,20)}
    System.out.println(rangeSet.toString());

    rangeSet.remove(Range.open(5, 10)); //分割[1, 10]; {[1,5], [10,10], [11,20)}
    System.out.println(rangeSet.toString());

}
           

RangeSet 的實作支援非常廣泛的視圖:

  • complement():傳回 RangeSet 的補集視圖。complement 也是 RangeSet 類型, 包含了不相連的、非空的區間。
  • subRangeSet(Range):傳回 RangeSet 與給定 Range 的交集視圖。這擴充了傳統排序集合中的 headSet、subSet 和 tailSet 操作。
  • asRanges():用 Set<Range> 表現 RangeSet,這樣可以周遊其中的 Range。
  • asSet(DiscreteDomain)(僅 ImmutableRangeSet 支援):用 ImmutableSortedSet 表現 RangeSet,以區間中所有元素的形式而不是區間本身的形式檢視。(這個操作不支援 DiscreteDomain 和 RangeSet 都沒有上邊界,或都沒有下邊界的情況)

RangeSet 的查詢方法

為了友善操作,RangeSet 直接提供了若幹查詢方法,其中最突出的有:

  • contains©:RangeSet 最基本的操作,判斷 RangeSet 中是否有任何區間包含給定元素。
  • rangeContaining©:傳回包含給定元素的區間;若沒有這樣的區間,則傳回 null。
  • encloses(Range):簡單明了,判斷 RangeSet 中是否有任何區間包括給定區間。
  • span():傳回包括 RangeSet 中所有區間的最小區間。

7. RangeMap

RangeMap描述了”不相交的、非空的區間”到特定值的映射

和RangeSet不同,RangeMap不會合并相鄰的映射,即便相鄰的區間映射到相同的值

public static void rangeMap() {
    RangeMap<Integer, String> rangeMap = TreeRangeMap.create();

    rangeMap.put(Range.closed(1, 10), "foo"); //{[1,10] => "foo"}
    System.out.println(rangeMap.toString());

    rangeMap.put(Range.open(3, 6), "bar"); //{[1,3] => "foo", (3,6) => "bar", [6,10] => "foo"}
    System.out.println(rangeMap.toString());

    rangeMap.put(Range.open(10, 20), "foo"); //{[1,3] => "foo", (3,6) => "bar", [6,10] => "foo", (10,20) => "foo"}
    System.out.println(rangeMap.toString());

    rangeMap.remove(Range.closed(5, 11)); //{[1,3] => "foo", (3,5) => "bar", (11,20) => "foo"}
    System.out.println(rangeMap.toString());

    /**
     * RangeMap的視圖
     */
    // 用Map<Range<K>, V>表現RangeMap。這可以用來周遊RangeMap
    Map<Range<Integer>, String> mapOfRanges = rangeMap.asMapOfRanges();
    System.out.println(mapOfRanges);

    // 用RangeMap類型傳回RangeMap與給定Range的交集視圖
    RangeMap<Integer, String> subRangeMap = rangeMap.subRangeMap(Range.open(12, 18));
    System.out.println(subRangeMap);
}
           

三、集合工具類

任何對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

1. 靜态工廠方法

public static void staticConstructors() {

    // Guava提供了能夠推斷範型的靜态工廠方法
    List<Integer> list = Lists.newArrayList();
    Map<String, String> map = Maps.newLinkedHashMap();

    // 用工廠方法模式,我們可以友善地在初始化時就指定起始元素
    Set<Integer> copySet = Sets.newHashSet(1, 2);
    List<String> theseElements = Lists.newArrayList("alpha", "beta", "gamma");

    // 通過為工廠方法命名(Effective Java第一條),我們可以提高集合初始化大小的可讀性
    List<Integer> exactly100 = Lists.newArrayListWithCapacity(100);
    List<Integer> approx100 = Lists.newArrayListWithExpectedSize(100);
    Set<Integer> approx100Set = Sets.newHashSetWithExpectedSize(100);

    // Guava引入的新集合類型沒有暴露原始構造器,也沒有在工具類中提供初始化方法。而是直接在集合類中提供了靜态工廠方法
    Multiset<String> multiset = HashMultiset.create();

}
           

2. Iterables工具類

public static void iterable() {
    /**
     * 正常方法
     */
    // 串聯多個iterables的懶視圖
    // 懶視圖意味着如果還沒通路到某個iterable中的元素,則不會對它進行串聯操作
    Iterable<Integer> concatenated = Iterables.concat(
            Ints.asList(1, 2, 3),
            Ints.asList(4, 5, 6));
    // [1, 2, 3, 4, 5, 6]
    System.out.println(concatenated);

    // 傳回對象在iterable中出現的次數
    int num = Iterables.frequency(concatenated, 1);
    // 1
    System.out.println(num);

    // 把iterable按指定大小分割,得到的子集都不能進行修改操作
    Iterable<List<Integer>> partition = Iterables.partition(concatenated, 2);
    // [[1, 2], [3, 4], [5, 6]]
    System.out.println(partition);

    // 傳回iterable的第一個元素,若iterable為空則傳回預設值
    int firstValue = Iterables.getFirst(concatenated, 0);
    // 1
    System.out.println(firstValue);

    // 傳回iterable的最後一個元素,若iterable為空則抛出NoSuchElementException
    int lastValue = Iterables.getLast(concatenated, 0);
    // 6
    System.out.println(lastValue);

    // 如果兩個iterable中的所有元素相等且順序一緻,傳回true
    Iterable<Integer> other = Iterables.concat(
            Ints.asList(4, 5, 6),
            Ints.asList(1, 2, 3));
    // [4, 5, 6, 1, 2, 3]
    System.out.println(other);
    boolean same = Iterables.elementsEqual(concatenated, other);
    // false
    System.out.println(same);

    // 傳回iterable的不可變視圖
    Iterable<Integer> unmodifiableIterable = Iterables.unmodifiableIterable(concatenated);
    // [1, 2, 3, 4, 5, 6]
    System.out.println(unmodifiableIterable);

    // 限制iterable的元素個數限制給定值
    Iterable<Integer> limitIterable = Iterables.limit(concatenated, 1);
    // [1]
    System.out.println(limitIterable);

    // 擷取iterable中唯一的元素,如果iterable為空或有多個元素,則快速失敗
    int value = Iterables.getOnlyElement(limitIterable);
    // 1
    System.out.println(value);


    /**
     * 與Collection方法相似的工具方法
     */
    List numbers = Lists.newArrayList(-1, 0);

    Iterables.addAll(numbers, concatenated);
    // [-1, 0, 1, 2, 3, 4, 5, 6]
    System.out.println(numbers);

    boolean contains = Iterables.contains(concatenated, 1);
    // true
    System.out.println(contains);

    boolean removeAll = Iterables.removeAll(numbers, Lists.newArrayList(6, 9));
    // true
    System.out.println(removeAll);
    // [-1, 0, 1, 2, 3, 4, 5]
    System.out.println(numbers);

    numbers = Lists.newArrayList(-1, 0);
    boolean retainAll = Iterables.retainAll(numbers, Lists.newArrayList(0));
    // true
    System.out.println(retainAll);
    // [0]
    System.out.println(numbers);

    int size = Iterables.size(concatenated);
    // 6
    System.out.println(size);

    Integer[] array = Iterables.toArray(concatenated, Integer.class);
    // 1 2 3 4 5 6
    for (Integer integer : array) {
        System.out.print(integer + " ");
    }
    System.out.println();

    boolean isEmpty = Iterables.isEmpty(Lists.newArrayList());
    // true
    System.out.println(isEmpty);

    int one = Iterables.get(concatenated, 1);
    // 2
    System.out.println(one);

    // [1, 2, 3, 4, 5, 6]
    String str = Iterables.toString(concatenated);
    System.out.println(str);
}
           

3. Lists工具類

/**
 * Lists工具類
 */
public static void lists() {

    List countUp = Ints.asList(1, 2, 3, 4, 5);

    List countDown = Lists.reverse(countUp);
    // {5, 4, 3, 2, 1}
    System.out.println(countDown);


    List<List> parts = Lists.partition(countUp, 2);
    // {{1,2}, {3,4}, {5}}
    System.out.println(countDown);

    /**
     * Lists提供如下靜态工廠方法
     */
    List list1 = Lists.newArrayList();
    List list2 = Lists.newArrayList(1, 2);
    List list3 = Lists.newArrayList(Iterables.concat());
    List list4 = Lists.newArrayList(Ints.asList(1).iterator());
    // 配置設定一個容量為10的數組
    List list5 = Lists.newArrayListWithCapacity(10);
    // 5L + arraySize + (arraySize / 10) = 16 配置設定一個容量為16的數組
    List list6 = Lists.newArrayListWithExpectedSize(10);

    LinkedList<Integer> linkedList1 = Lists.newLinkedList();
    LinkedList linkedList2 = Lists.newLinkedList(Iterables.concat());

}

           

4. Sets工具類

/**
     * 集合運算方法
     */

    Set<String> wordsWithPrimeLength = ImmutableSet.of("one", "two", "three", "six", "seven", "eight");
    Set<String> primes = ImmutableSet.of("two", "three", "five", "seven");

    // 交集運算
    Sets.SetView<String> intersection = Sets.intersection(primes, wordsWithPrimeLength);
    // [two, three, seven]
    System.out.println(intersection);

    // 并集運算
    Sets.SetView<String> union = Sets.union(primes, wordsWithPrimeLength);
    // [two, three, five, seven, one, six, eight]
    System.out.println(union);

    // 差集運算
    Sets.SetView<String> difference = Sets.difference(wordsWithPrimeLength, primes);
    Sets.SetView<String> difference2 = Sets.difference(primes, wordsWithPrimeLength);
    // [one, six, eight]
    System.out.println(difference);
    // [five]
    System.out.println(difference2);

    // 對稱差運算
    Sets.SetView<String> symmetricDifference = Sets.symmetricDifference(wordsWithPrimeLength, primes);
    Sets.SetView<String> symmetricDifference2 = Sets.symmetricDifference(primes, wordsWithPrimeLength);
    // [one, six, eight, five]
    System.out.println(symmetricDifference);
    // [five, one, six, eight]
    System.out.println(symmetricDifference2);


    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]]
    System.out.println(product);

    // 傳回給定集合的所有子集
    Set<Set<String>> animalSets = Sets.powerSet(animals);
    // [] [gerbil] [hamster] [gerbil, hamster]
    animalSets.forEach(v -> System.out.print(v + " "));
    System.out.println();


    /**
     * SetView也實作了Set接口,可直接當作Set使用
     */

    // 對自己做不可變拷貝
    ImmutableSet<String> immutableCopy = intersection.immutableCopy();
    // [two, three, seven]
    System.out.println(immutableCopy);

    // 拷貝進另一個可變集合
    Set<String> set = intersection.copyInto(Sets.newHashSet("one"));
    // [seven, two, three, one]
    System.out.println(set);


    /**
     * 靜态工廠方法
     */

    /**
     * HashSet
     */
    ch01_basic
    Set<Integer> hashSet1 = Sets.newHashSet();
    // with elements
    Set<Integer> hashSet2 = Sets.newHashSet(1, 2);
    // from Iterable
    Set<Integer> hashSet3 = Sets.newHashSet(Iterables.concat());
    // from Iterator
    Set<Integer> hashSet4 = Sets.newHashSet(Ints.asList().iterator());
    // with expected size
    Set<Integer> hashSet5 = Sets.newHashSetWithExpectedSize(10);

    /**
     * LinkedHashSet
     */
    ch01_basic
    Set<Integer> linkedHashSet1 = Sets.newLinkedHashSet();
    // from Iterable
    Set<Integer> linkedHashSet2 = Sets.newLinkedHashSet(Iterables.concat());
    // with expected size
    Set<Integer> linkedHashSet3 = Sets.newLinkedHashSetWithExpectedSize(10);

    /**
     * TreeSet
     */
    ch01_basic
    Set<Integer> treeSet1 = Sets.newTreeSet();
    // from Iterable
    Set<Integer> treeSet2 = Sets.newTreeSet(Iterables.concat());

    // rom Iterable
    Set<Integer> treeSet3 = Sets.newTreeSet(Comparator.comparingInt(o -> o));
    treeSet3.addAll(Ints.asList(1, 2, 3));
    // [1, 2, 3]
    System.out.println(treeSet3);

    treeSet3 = Sets.newTreeSet((o1, o2) -> o2 - o1);
    treeSet3.addAll(Ints.asList(1, 2, 3));
    // [3, 2, 1]
    System.out.println(treeSet3);
    
           

5. Maps工具類

/**
     * uniqueIndex
     * 通常針對的場景是:有一組對象,它們在某個屬性上分别有獨一無二的值,而我們希望能夠按照這個屬性值查找對象
     */
    // 比方說,我們有一堆字元串,這些字元串的長度都是獨一無二的,而我們希望能夠按照特定長度查找字元串
    List<String> ch06_strings = Lists.newArrayList("aaa", "bb");
    ImmutableMap<Integer, String> stringsByIndex = Maps.uniqueIndex(ch06_strings, new Function<String, Integer>() {
        public Integer apply(String string) {
            return string.length();
        }
    });

    /**
     * difference
     * 用來比較兩個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);

    // 兩個Map中都有的映射項,包括比對的鍵與值
    Map<String, Integer> entriesInCommon = diff.entriesInCommon();
    // {b=2}
    System.out.println(entriesInCommon);

    // 鍵相同但是值不同值映射項
    Map<String, MapDifference.ValueDifference<Integer>> entriesDiffering = diff.entriesDiffering();
    // {c=(3, 4)}
    System.out.println(entriesDiffering);

    // 鍵隻存在于左邊Map的映射項
    Map<String, Integer> entriesOnlyOnLeft = diff.entriesOnlyOnLeft();
    // {a=1}
    System.out.println(entriesOnlyOnLeft);

    // 鍵隻存在于右邊Map的映射項
    Map<String, Integer> entriesOnlyOnRight = diff.entriesOnlyOnRight();
    // {d=5}
    System.out.println(entriesOnlyOnRight);

    /**
     * Maps類中處理BiMap的工具方法
     * BiMap : 既提供鍵到值的映射,也提供值到鍵的映射,是雙向Map
     */
    BiMap<Integer, String> logfileMap = HashBiMap.create();
    logfileMap.put(1,"a.log");
    logfileMap.put(2,"b.log");

    // 傳回一個同步的(線程安全)的bimap,由給定的bimap支援
    BiMap<Integer, String> synchronizedBiMap = Maps.synchronizedBiMap(logfileMap);

    // 傳回給定的bimap的不可修改的BiMap表示
    BiMap<Integer, String> unmodifiableBiMap = Maps.unmodifiableBiMap(logfileMap);


    /**
     * Maps提供的靜态工廠方法
     */

    /**
     * HashMap
     */
    // basic
    Map<String, String> hashMap1 = Maps.newHashMap();
    // from Map
    Map<String, String> hashMap2 = Maps.newHashMap(Maps.newHashMap());
    // with expected size
    Map<String, String> hashMap3 = Maps.newHashMapWithExpectedSize(10);

    /**
     * LinkedHashMap
     */
    // basic
    Map<String, String> linkedHashMap1 = Maps.newLinkedHashMap();
    // from Map
    Map<String, String> linkedHashMap2 = Maps.newLinkedHashMap(Maps.newHashMap());

    /**
     * TreeMap
     */
    // basic
    Map<String, String> treeMap1 = Maps.newTreeMap();
    // from Comparator
    Map<String, String> treeMap2 = Maps.newTreeMap(new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            return o1.length() - o2.length();
        }
    });
    // from SortedMap
    Map<String, String> treeMap3 = Maps.newTreeMap(Maps.newTreeMap());

    /**
     * EnumMap
     */
    // from Class
    Map<DayOfWeek, Integer> map =  Maps.newEnumMap(DayOfWeek.class);
    map.put(MONDAY, 1);
    // from Map
    EnumMap enumMap = new EnumMap(ImmutableMap.of(MONDAY, 1));
    enumMap.put(TUESDAY, 2);

    /**
     * ConcurrentMap
     */
    // basic
    // 支援所有操作
    ConcurrentMap<String, String> concurrentHashMap = Maps.newConcurrentMap();

    /**
     * IdentityHashMap
     * 值可以重複的map
     * 在IdentityHashMap中,是判斷key是否為同一個對象,而不是普通HashMap的equals方式判斷
     */
    // basic
    IdentityHashMap<String, String> identityHashMap1 = Maps.newIdentityHashMap();
    identityHashMap1.put(new String("yyy"), "1");
    identityHashMap1.put(new String("yyy"), "2");
    identityHashMap1.put(new String("xxx"), "3");
    // {yyy=2, yyy=1, xxx=3}
    System.out.println(identityHashMap1);

    IdentityHashMap<String, String> identityHashMap2 = Maps.newIdentityHashMap();
    identityHashMap2.put("yyy", "1");
    identityHashMap2.put("yyy", "2");
    identityHashMap2.put("xxx", "3");
    // {xxx=3, yyy=2}
    System.out.println(identityHashMap2);
        
           

6. Multisets工具類

/**
     * index(Iterable, Function)
     * Maps.uniqueIndex的兄弟方法
     * 通常針對的場景是:有一組對象,它們有共同的特定屬性,我們希望按照這個屬性的值查詢對象,但屬性值不一定是獨一無二的
     */
    ImmutableSet<String> digits = ImmutableSet.of(
            "zero", "one", "two", "three", "four",
            "five", "six", "seven", "eight", "nine");
    ImmutableListMultimap<Integer, String> digitsByLength = Multimaps.index(digits, string -> string.length());
    // {4=[zero, four, five, nine], 3=[one, two, six], 5=[three, seven, eight]}
    System.out.println(digitsByLength);

    /**
     * invertFrom(Multimap toInvert, Multimap dest)
     */
    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.create());
    // {1=[a], 2=[a, b, c], 3=[c], 4=[a, b], 5=[c], 6=[b]}
    System.out.println(inverse);

    // 如果使用的是ImmutableMultimap,考慮改用ImmutableMultimap.inverse()做反轉
    ImmutableMultimap inverse2 = ImmutableMultimap.of(
            "b", Ints.asList(2, 4, 6),
            "a", Ints.asList(4, 2, 1),
            "c", Ints.asList(2, 5, 3));
    // {[2, 4, 6]=[b], [4, 2, 1]=[a], [2, 5, 3]=[c]}
    System.out.println(inverse2.inverse());

    /**
     * forMap(Map)
     * 把Map包裝成SetMultimap
     */
    Map<String, Integer> map = ImmutableMap.of("a", 1, "b", 1, "c", 2);
   // 把Map包裝成SetMultimap
    SetMultimap<String, Integer> setMultimap = Multimaps.forMap(map);
    // {a=[1], b=[1], c=[2]}
    System.out.println(setMultimap);


    // 與Multimaps.invertFrom結合使用,可以把多對一的Map反轉為一對多的Multimap
    Multimap<Integer, String> inverseMap = Multimaps.invertFrom(setMultimap, HashMultimap.create());
    // {1=[a, b], 2=[c]}
    System.out.println(inverseMap);
    
           

7. 包裝器

8. Tables工具類

四、集合擴充工具類

1. Forwarding裝飾器

通過建立ForwardingXXX的子類并實作delegate()方法,可以選擇性地覆寫子類的方法來增加裝飾功能,而不需要自己委托每個方法

// 在元素被添加到清單時增加特定的行為
AddLoggingList<Integer> loggingList = new AddLoggingList<>();
loggingList.add(1);

/**
 * Forwarding裝飾器
 * 通過建立ForwardingXXX的子類并實作delegate()方法,可以選擇性地覆寫子類的方法來增加裝飾功能,而不需要自己委托每個方法
 * @author
 * @create 2018-05-07 下午11:33
 **/
class AddLoggingList<E> extends ForwardingList<E> {
    final List<E> delegate = Lists.newArrayList(); // backing list
    @Override
    protected List<E> delegate() {
        return delegate;
    }
    @Override
    public void add(int index, E elem) {
        log(index, elem);
        super.add(index, elem);
    }
    @Override
    public boolean add(E elem) {
        return standardAdd(elem); // implements in terms of add(int, E)
    }
    @Override
    public boolean addAll(Collection<? extends E> c) {
        return standardAddAll(c); // implements in terms of add
    }

    private void log(int index, E elem) {
        System.out.println(String.format("add %s in %d", elem, index));
    }
}
           

2. PeekingIterator

Iterators提供一個Iterators.peekingIterator(Iterator)方法,來把Iterator包裝為PeekingIterator,這是Iterator的子類,它能讓你事先窺視[peek()]到下一次調用next()傳回的元素。

注意:Iterators.peekingIterator傳回的PeekingIterator不支援在peek()操作之後調用remove()方法

// 舉個例子:複制一個List,并去除連續的重複元素
List<Integer> numbers = Ints.asList(1, 1, 2, 3, 3);
List<Integer> results = Lists.newArrayList();

PeekingIterator<Integer> iterator = Iterators.peekingIterator(numbers.iterator());
while (iterator.hasNext()) {
    Integer current = iterator.next();
    while (iterator.hasNext() && iterator.peek().equals(current)) {
        // skip this duplicate element
        iterator.next();
    }
    results.add(current);
}
// [1, 2, 3]
System.out.println(results);
           

3. AbstractIterator

實作自己的Iterator, 例如包裝一個iterator跳過長度為1的字元串

Iterator<String> skipNulls2 = skipSingle(Lists.newArrayList("aa", "b", "cc").iterator());
// aa cc
while (skipNulls2.hasNext()) {
    System.out.print(skipNulls2.next() + " ");
}

// 包裝一個iterator跳過長度為1的字元串
public static Iterator<String> skipSingle(final Iterator<String> in) {
    return new AbstractIterator<String>() {
        protected String computeNext() {
            while (in.hasNext()) {
                String s = in.next();
                if (s.length() > 1) {
                    return s;
                }
            }
            return endOfData();
        }
    };
}