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不可变版本
CollectionJDKImmutableCollection
ListJDKImmutableList
SetJDKImmutableSet
SortedSet/NavigableSetJDKImmutableSortedSet
MapJDKImmutableMap
SortedMapJDKImmutableSortedMap
MultisetGuavaImmutableMultiset
SortedMultisetGuavaImmutableSortedMultiset
MultimapGuavaImmutableMultimap
ListMultimapGuavaImmutableListMultimap
SetMultimapGuavaImmutableSetMultimap
BiMapGuavaImmutableBiMap
ClassToInstanceMapGuavaImmutableClassToInstanceMap
TableGuavaImmutableTable

二、新集合类型

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元素
HashMapHashMultiset
TreeMapTreeMultiset是(如果comparator支持的话)
LinkedHashMapLinkedHashMultiset
ConcurrentHashMapConcurrentHashMultiset
ImmutableMapImmutableMultiset

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> 的地方,你都可以使用它们,即把键映射到任意多个值的一般方式:

实现键行为类似值行为类似
ArrayListMultimapHashMapArrayList
HashMultimapHashMapHashSet
LinkedListMultimap*LinkedHashMap*LinkedList*
LinkedHashMultimap**LinkedHashMapLinkedHashMap
TreeMultimapTreeMapTreeSet
ImmutableListMultimapImmutableMapImmutableList
ImmutableSetMultimapImmutableMapImmutableSet

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实现
HashMapHashMapHashBiMap
ImmutableMapImmutableMapImmutableBiMap
EnumMapEnumMapEnumBiMap
EnumMapHashMapEnumHashBiMap

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 工具类
CollectionJDKCollections2:不要和 java.util.Collections 混淆
ListJDKLists
SetJDKSets
SortedSetJDKSets
MapJDKMaps
SortedMapJDKMaps
QueueJDKQueues
MultisetGuavaMultisets
MultimapGuavaMultimaps
BiMapGuavaMaps
TableGuavaTables

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();
        }
    };
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值