Guava的基础功能与集合

Guava

文章目录

一、 guava 是什么

guava是来自Google的Java核心类库。包含了新的集合类型(例如:复合map、复合set)、不可变集合,以及一些对于并发、I/O、hashing、缓存、原型、字符串等的通用功能。guava被广泛使用在Google的项目中,也被广泛的使用在其他公司里。

二、 基础功能:更开心的使用Java语言

2.1 使用和避免null

null 是一个模棱两可的概念,能引起让人困惑的错误。很多Guava的通用功能在遇到null的时候会拒绝并快速失败。而不是盲目的接受它们。

####(1)使用和避免null:Optional

粗心使用null可能造成令人震惊的各种错误。

另外,null是一个令人不愉快的模糊概念,很少能从返回的null值明确的推测出其代表的含义。例如Map.get(key),返回null,既可以代表在map中的值是null,也可以代表value不再map中。null即可以代表失败,也是代表成功,它能代表几乎任何东西。使用null以外的东西,可以让意思更加明确。

Guava提供了很多特征,不仅帮助你让在需要使用null的时候轻松使用null,也让你避免使用null。

Optional:

  • 创建Optionnal,静态方法

    • Optional.of(T t) 当遇到null值当时候,就会快速失败;
    • Optional.obsent() 创建某一个缺少某个值当类型当对象;
    • Optional.fromNullable(T t) 将一个可能为非空的引用转换为Optional;
  • 查询方法,非静态方法

    • optional.isPresent() 判断是否存在
    • optional.get() 如果不存在将抛出异常
    • optional.or(T t) 如果不存在返回定义的默认值
    • optional.orNull() 如果不存在返回null
    • optional.asSet() 返回一个不可变的集合,有一值,或者一个空集合
  • 重点是什么?

    除了通过null值一个名称增加了可读性,最大的优势的它的操作是白痴操作。如果你想让你的程序在任何时候都能编译通过,它强迫你去考虑值不存在的情况,因为你不得不积极的展开Optional并去处理这种情况。

    它是极其相关的,当返回的值既可能是存在也可能不存在的时候。比起你要实现other.method方法的时候忘记a可能为null值,你更容器忘了other.method(a, b)的返回值是null。让返回值是Optional类型,就会避免这种情况的发生。

  • 便利的方法

    • MoreObjects.firstNonNull(T t, T t2) 无论什么时候你想让一个null值被一个默认值代替,使用MoreObjects.firstNonNull。如果两个值都为空,将报空指针异常。

      如果使用Optional,则有一个更好的代替方案,or(T t)

    一些用于处理String类型null的方法,是在Strings中提供,具体来说,我们提供了如下的名称:

    • Strings.nullToEmpty(String)
    • Strings.isNullOrEmpty(String)
    • Strings.emptyToNull(String)

    我们想要强调这些方法主要是为了面对令人不愉快的APIs交互,它们等同于null 字符串或空字符串。每次你要写代码合并null 字符串和空字符串的时候,Guava团队都在哭泣。(如果将null字符串和空字符串作为不同的东西对待,那是好多,如果将它们作为相同的东西对待就是会有令人不安的代码味道。)

2.2 先决条件:Preconditions

让方法更容易测试先决条件。

(1)Preconditions 类

Guava提供了很多先决条件的检查工具在Preconditions类中。强烈推荐通过静态导入的方式使用它们。

每一个方法都有三个变种:

  • 没有额外的参数,任何异常将会抛出,不带任何错误信息;

  • 带有一个额外的Object参数,任何异常将会抛出,带有一个object.toString的错误消息;

  • 带有一个额外的String类型的参数,并带有任意数量的额外的Object参数。这种行为有时候更像printf,但是它为了GWT的兼容性和高效性,仅仅允许%s标识符。

    注意:checkNotNullcheckArgumentcheckState又很多的重载方法,获取原始类型和对象参数,而不是一个可变数组。在绝大多数情况下,这种调用可以避免上述调用时进行原始类型的装箱和可变数组的分配。

第三种变体的示例代码:

import static com.google.common.base.Preconditions.*;
public class UsingAndAvoidNull {
    public void preconditionTest(int i, int j) {
        checkArgument(i>0, "Argument was %s but expected nonnegative", i);
        checkArgument(i>j, "Expected i > j, but %s >= %s", i, j);
    }
}
方法签名(不包含额外的参数)描述在失败的时候抛出异常
checkArgument(boolean)检查boolean是true,用于验证方法的参数。IllegalArgumentException
checkNotNull(T)检查值不为null,如果在行内使用checkNotNull(value),将直接返回value值。NullPointerException
checkState(boolean)检查对象的状态,不依赖方法的参数。例如,一个Iterator可以使用这个方法检查next有值能够调用,在调用remove之前IllegalStateException
checkElementIndex(int index, int size)检查索引是有效的,对于list、String、或者指定大小的数组。元素的索引从0(包含)到size(不包含)。不能直接传递list、string、或数组。仅传递它的大小,返回索引值。IndexOutOfBoundsException
checkPositionIndex(int index, int size)检查是有一个位置,对于list、string、或指定大小的数组。位置的值从0(包含)到size(不包含)。不能直接传递list、string、数组,只需要传递它们的大小。最后返回index。IndexOutOfBoundsException
checkPositionIndexes(int start,int end, int index)检查index是否在[start, end]范围内,其中end值要大于等于start。IndexOutOfBoundsException

可变参数“printf-style”风格的异常消息。这个优势是我们推荐使用checkNotNull,而不是Objects.requireNotNull的原因。

建议将先决条件分成不同的行,它能帮助你在调试期间定位出哪个先决条件失败了。此外,你应该提供有用的错误信息。

(2)有条件的失败

一个有条件的失败或者运行时检查,是任何一段代码当且仅当一个boolean条件成立的时候抛出异常。在好的软件设计中这种代码很常见。

一个非常容易的处理,可以用相同的方法:if(!condition) {throw new RunTimeException();} 。但是如果你花一点时间去考虑你正在执行的检查性质,然后用更合适的方式处理它,就能让你的代码更容易理解,错误也更容易诊断。

这里有一些重要的运行时检查:

  • 先觉条件的检查。

    确认调用的公共方法是服从方法规范要求的。例如,一个sqrt函数,仅接受非负参数。

    IllegalArgumentException、IllegalStateException

  • 传统的断言检查。

    仅应该在以某种方式类本身被破坏的时候失败。

    assert、AssertionError

  • 确认检查。

    当你对一个要使用的API严重缺乏信心的时候,要对它进行检查。

    VerifyException

  • 测试断言。仅仅在测试类中被发现,确保测试代码是符合规范要求的。

    注意,这类断言的代码和生产代码的断言几乎没有共同之处。

    assertThat、assertEquals、AssertionError

  • 不可能条件检查。它是不可能失败,除非我们代码被修改了,或者严重违反了底层平台的行为。

    AssertionError

  • 一个异常的结果。方法没有提供预期的结果

2.3 排序:Ordering

Guava有强大的Comparator类。

从本质上讲,Ordering 无非是一个Comparator实现。Ordering 依赖于Collector的方法(例如,Collections.max)。此外Ordering通过链式方法来增强和调整现有的比较器。

(1)常见的排序通过静态方法提供
  • Ordering.natural() 对可比较类型进行自然排序
  • Ordering.usingToString() 根据toString()返回的字符串表示形式的字典顺序比较对象。
(2)将一个预先存在的Comparator变成Ordering
  • Ordering.from(Comparator)
(3)创建一个自定义的Ordering:直接扩展Ordering
        Ordering byLenOrdering = new Ordering<String>() {
            @Override
            public int compare(String s1, String s2) {
                return Integer.compare(s1.length(), s2.length());
            }
        };
(4)比较器链

通过包装Ordering获取派生的顺序。一些常用的变体包含如下:

  • reverse() 逆序

  • nullsFirst() null值放到最前面,非空值按照Ordering定义的排序进行排序;

  • nullsLast() null值放在最后,非空值按照Ordering定义的排序进行排序;

  • compound() 组合两个比较器,当第一个比较器无法比较大小时,采用第二个比较器进行比较;

    // 按照字符串的长度进行比较,如果长度相同按照字母表顺序进行比较
    Collections.sort(names, byLenOrdering.compound(Ordering.usingToString()));
    
  • lexicographical() 对Iterator列表进行排序,类比Ordering.usingToString() ,这里每个可对比的单元是Iterator中的一个元素。

            List<List<String>> groups = new ArrayList<>();
            groups.add(new ArrayList<>(Arrays.asList("aa", "ff")));
            groups.add(new ArrayList<>(Arrays.asList("aa", "bb", "cc")));
            System.out.println(groups);  // [[aa, ff], [aa, bb, cc]]
            Collections.sort(groups, Ordering.usingToString().lexicographical());
            System.out.println(groups);  // [[aa, bb, cc], [aa, ff]]
    
  • onResultOf(Function) 把比较器的元素使用Function函数转化成一个值result,再对这个值应用Ordering的比较方法。result的排序顺序就是最后的排序顺序。

例如,你要对Foo列表进行排序:

class Foo {
  String sortedBy;
  int notSortedBy;
}

Ordering<Foo> ordering = Ordering.natural().nullsFirst().onResultOf((Foo foo)->foo.sortedBy);
(5)比较器链的调用:从右往左的调用
Ordering<Foo> ordering = Ordering.natural().nullsFirst().onResultOf((Foo foo)->foo.sortedBy);

比如上面的调用器链,先查找Foo的sortedBy字段,然后将所有null元素移动到最前面,再对剩下的顺序进行自然排序。

之所以,出现这个从右往左的调用方式,是因为每个调用链都是将前面一个Ordering包装成一个新的Ordering。

注意:compound() 是调用链里的例外,它是从左往右调用。

调用链太长,不容易理解。推荐把调用链控制在三个以内。

####(6)应用

  • greatestOf(Iterable iterable, int k) 对元素按照从大到小排序,并返回前k个元素

    List<Integer> result = Ordering.natural().greatestOf(new ArrayList<>(Arrays.asList(3, 5, 1)), 2);
    
  • leastOf(Iterable iterable, int k) 对元素按照从小到大排序,并返回前k个元素

  • isOrdered(Iterable iterable) 测试是否为按照递增的顺序排序

    boolean ordered = Ordering.natural().isOrdered(result);
    
  • isStrictlyOrdered(Iterable iterable) 测试是否按照递减的顺序排序

  • sortedCopy(Iterable iterable) 返回一个新的已经排序的列表,原来的列表顺序不会变

            List<Integer> nums = new ArrayList<>(Arrays.asList(4, 1, 3));
            List<Integer> resultCopy = Ordering.natural().sortedCopy(nums);
            System.out.println(resultCopy); // [1, 3, 4]
            System.out.println(nums); // [4, 1, 3]
    
  • immutableSortedCopy(Iterable iterable) 类似sortedCopy,只不过返回一个不可变的副本

  • min(E e1, E e2) 返回最小的值,如果两个值相等,返回第一个元素。

    Integer min = Ordering.natural().min(5, 1);
    
  • max(E e1, E e2) 返回最大值,如果两个值相等,返回第一个元素。

  • min(E, E, E...) 对比多个值,返回最小值,如果有多个最小值,返回第一个

  • max(E, E, E...) 对比多个值,返回最大值,如果有多个最大值,返回第一个

  • min(Iterable)max(Iterable) 返回列表中的最小值或最大值,如果列表为空,抛出异常。

2.4 Objects中通用的对象方法:equals、hashCode、toString

简化了实现对象的方法,例如hashcode()、toString()。

(1)Objects.equal()

可以避免在比较时由于null值带来麻烦。在JDK7中也引入的Objects,里面的equals() 方法是等价的。

Objects.equal("a", "a"); // returns true
Objects.equal(null, "a"); // returns false
Objects.equal("a", null); // returns false
Objects.equal(null, null); // returns true
(2)Objects.hashCode(filed1, filed2,...,filedn)

对一系列顺序的字段进行hash。

在JDK7中引入的Objects中,里面的hash() 方法和上面是等价的。

(3)toString()MoreObjects.toStringHelper()

toString() 方法对于调试代码没有用,但很难写,可以使用MoreObjects.toStringHelper()方法来创建一个有用的toString方法。

// MyObject{x=1, y=cc}
MoreObjects.toStringHelper("MyObject")
                .add("x", 1)
                .add("y", "cc")
                .toString();
(4)compare/compareTo:ComparisonChain

在实现Comparator 或 Comparable的时候非常痛苦,可以使用ComparisonChain来进化工作:

    @Override
    public int compareTo(Product o) {
        return ComparisonChain.start()
                .compare(this.name, o.name)
                .compare(this.num, o.name)
                .compare(this.typeEnum, o.typeEnum, Ordering.natural())
                .result();
    }

ComparisonChain 采用懒工作模式:它执行比较,直到找到一个非零的结果,然后忽略进一步的输入。

2.5 异常:Throwables

Guava中的Throwables能够简化异常的处理。

(1)传播异常
  • Throwables.propagateIfPossible(Throwable, Class<X extends Throwable>) throws X; 只有当它是RuntimeException、Error、或X的时候,它才会按照原样抛出异常
  • Throwables.throwIfInstanceOf(Throwable, Class<x extends Exeption>) throws X; 当且仅当它是X类型的异常时,按照原样传播可抛出异常
  • Throwables.throwIfUnchecked(Throwable) 仅当它是RuntimeExeption或Error的时候,才按照原样抛出异常
(2)异常的因果关系
  • Throwables.getRootCause(Throwable) 获取根部异常
  • Throwables.getCausalChain(Throwable) 获取异常链
  • Throwables.getStackTraceAsString(Throwable) 将异常转化成字符串
            Throwable throwable = new IllegalArgumentException(new ArrayStoreException());
            Throwable rootCause = Throwables.getRootCause(throwable); // java.lang.ArrayStoreException
            System.out.println(rootCause);
						// [java.lang.IllegalArgumentException: java.lang.ArrayStoreException, java.lang.ArrayStoreException]
            System.out.println(Throwables.getCausalChain(throwable));
            /*
            java.lang.IllegalArgumentException: java.lang.ArrayStoreException
	at com.hef.guava.baseutilities.ThrowDemo.main(ThrowDemo.java:16)
Caused by: java.lang.ArrayStoreException
	... 1 more
            */
            System.out.println(Throwables.getStackTraceAsString(throwable));

三、集合:Collections

Guava扩展了Java的集合生态系统,这是Guava中非常成熟和流程的一部分。

3.1 不可变集合:Immutable

(1)不可变集合的好处
  • 被不信任的类库使用是安全的
  • 线程安全:能够被多个线程实现,而没有共享问题;
  • 节省内存和时间
  • 可以作为常量使用

为了集合对象创建不可变的副本是一个好的防御性的编程策略。Guava为每一个Collection类型(包括Guava自己的Collection类)都提供了简单易用的不可变集合版本。

(2)JDK的Collections.unmodifiableXXX 产生不可变集合存在的问题
  • 笨重、冗长
  • 不安全:只有当没有人持有原始集合引用的时候,它返回的结合才真正是不可变的;
  • 低效:数据结构仍然保留着可变集合的开销,包括并发修改检查、在hash表中额外的空间等。
(3)不可变集合的使用场景(Guava的不可变集合中不允许null值)

当你不期望集合被修改,或者期望集合一致不变,一个好的策略是采用防御性拷贝,让其变成不可变集合。

重要:在Guava的不可变集合中,不允许有null值。如果你想使用带有null的不可变集合,可以使用Collections.unmodifiableXXX

(4)创建不可变集合的几个方法
  • 通过copyOf方法,例如:ImmutableSet.copyOf(set)

  • 使用of 方法,例如:ImmutableSet.of

    ImmutableMap.of("name", "world", "where", "beijing");
    ImmutableSet.of("aa", "bb")
    
  • 使用builder方法:

    ImmutableSet<String> result = ImmutableSet.<String>builder().add("aaa").add("ccc").build();
    

除了有顺序的集合,顺序是根据存入数据结构的先后顺序。

(5)copyOf 非常的聪明:能在安全的情况下,避免复制数据结构。

ImmutableXXX.copyOf 避免线性时间的复制:

  • 在恒定的时间,使用底层的数据结构。例如,ImmutableSet.copyOf(ImutableList), 不能再固定时间完成。
  • 它不会导致内存泄露。例如,如果有一个ImmutableList<String> hugeList,使用ImmutableList.copyOf(hugeList.subList(0, 10)),将会执行一个明确的拷贝。从而避免持有对hugeList不必要的引用;
  • 不会改变语义。例如,ImmutableSet.copyOf(myImmutableSortedSet)将执行明确的拷贝,因为hashCode和equals在ImmutableSet和ImmutableSorted中的语义是不一样的。

这将能为防御性编码风格带来最小的性能负担。

(6)asList

便可变集合通过asList方法就能得到一个ImmutableList

(7)J可变集合接口与Guava中的不可变集合对应
JDK中的接口JDK 还是 GuavaGuava中的不可变集合版本
CollectionJDKImmutableCollection
ListJDKImmutableList
SetJDKImmutableSet
SortedSet/NavigableSetJDKImmutableSortedSet
MapJDKImmutableMap
SortedMapJDKImmutable
MultiSetGuavaImmutableMultiSet
SortedMultiSetGuavaImmutableSortedMultiSet
MultimapGuavaImmutableMultimap
ListMultimapGuavaImmutableListMultimap
SetMultimapGuavaImmutableSetMultimap
BiMapGuavaImmutableBiMap
ClassToInstanceMapGuavaImmutableClassToInstanceMap
TableGuavaImmutableTable

3.2 新的集合类型

Guava中定义的很多新的集合,它们被广泛的使用。这些新的集合遵循JDK的集合接口约定。

(1)Multiset (接口)
Multiset 的使用

一个场景:统计单词在文章中出现的次数。传统的做法如下:

        Map<String, Integer> result = new HashMap<>();
        for (String word : words) {
            if (result.containsKey(word)) {
                result.put(word,result.get(word)+1);
            }else {
                result.put(word, 1);
            }
        }

这样做容易出错,还不利于统计出一些有用的信息,例如,单词的总数。

使用Multiset就可以很简单的这样写:

        Multiset<String> set = HashMultiset.create();
        set.addAll(words);
  • 当被当作了一个普通的Collection的时候,Multiset的行为类似一个无序的ArrayList;
    • 调用add(E)方法,添加一个指定的元素;
    • iterator() 方法,遍历每个元素每次出现(元素每出现一次就会被遍历一次);
    • size() 方法,获取所有元素出现的总次数;
    • addAll(Collection) 添加多个元素添加进去;
  • 额外的查询操作,已经查询性能,就跟Map<String, Integer> 一样:
    • count(Object) 返回某个元素出现的总次数。对于HashMultiset,count的时间复杂度为O(1);对于TreeMultiset,count的时间复杂度是O(logn);
    • entrySet() 返回Set<Multiset.entry<E>> ,工作机制类似于Map的entrySet;
    • elementSet() 返回不同元素的集合列表,类似于Map的keySet();
    • Multiset 实现的内存消耗是和不同元素的数量是成线性相关的;
    • setCount(E, int) 设置某个元素,出现的次数
    • remove(E, int) 将某个元素减少指定次数;
Multiset并不是一个Map

注意,Multiset并不是一个Map<E,Integer>,尽管它是Multiset的一部分。Multiset真的是Collection类型。其它值得注意的有:

  • Multiset仅仅拥有正数的count。没有元素的count值是负数。并且计数为0的元素,是不在Multiset中,它不会出现在elementSet() 和entrySet() 中;
  • multiset.size() 返回的是集合的大小。每次执行add操作,size都会加1;
  • multiset.iterator() 迭代的是每个元素的所有次数。也就是迭代的次数等于size;
  • Multiset 支持添加元素、移除元素,直接设置元素的个数。setCount(element, 0) 等于把一个元素出现的所有次数移除;
  • multiset.count(E) 对于不在Multiset中的元素,总是返回0;
Multiset的实现
mapMultiset是否支持null
HashMapHashMultisetYES
TreeMapTreeMultisetYES
LinkedHashMapLinkedHashMultisetYES
ConcurrentHashMapConcurrentHashMultisetNO
ImmutableMapImmutableMultisetNO
SortedMultiset

SortedMultiset 接口是Multiset的变体。它能有效的获取在特定范围的子集。例如:

SortedMultiset<Comparable<Integer>> result = set.subMultiset(3, BoundType.OPEN, 9, BoundType.CLOSED);

TreeMultiset 实现了 SortedMultiset接口。

(2)Multimap
Multimap介绍

Guava的Multimap很容见处理key到多个value的映射。

有两种思路理解Multimap概念:

  • 它是一个集合:里面是单个key到单个value到映射;
  • 作为key到多个value到映射;

一帮情况下,我们最好把Multimap接口作为第一视图,也可以用另一种视角,通过调用asMap()方法得到Map<K, Collection<V>>

非常重要的是,在Multimap中没有key会对应一个空集合。也就是说,一个key至少对应一个值,或者key不存在Multimap中。

很少直接使用Multimap接口,经常使用ListMultimap或SetMultimap,它们的key分别对应List和Set。

创建Multimap

创建Multimap最简单直接的方式是通过MultimapBuilder来创建:

ListMultimap<String, Integer> listMultimap =
                MultimapBuilder.hashKeys().arrayListValues().build();

也可以直接在实现类上使用create() 方法:

ArrayListMultimap<String, Integer> multimap = ArrayListMultimap.create();
Multimap 的修改
  • Multimap.get(key) 通过这个方法返回指定key关联的values;

修改Multimap底层的内容:

        ListMultimap<String, Integer> listMultimap =
                MultimapBuilder.hashKeys().arrayListValues().build();
        List<Integer> nums = listMultimap.get("name");
        System.out.println(nums);  // []
        nums.clear();
        nums.add(23);
        nums.add(21);
        System.out.println(listMultimap.get("name")); // [23, 21]

其它修改Multimap底层的方法:

  • put(K, V) 添加一个K 及相关的V;等价于multimap.get(K).add(V)
  • putAll(K, Iterable<V>) 添加K到每一个V的联系;等价于Iterables.addAll(multimap.get(K), Iterable<V>)
  • remove(K, V) 移除一个K到V的关系,如果Multimap改变了,返回true;等价于multimap.get(K).remove(V)
  • removeAll(K) 移除所有与K有关的关系,并返回合适的集合类型;等价于multimap.get(K).clear()
  • replaceValues(K, Iterable<V>) 清空现有的key相关的关系,并建立新的关系,返回原来与Key相关联的value集合;等价于multimap.get(K).clear(); Iterables.addAll(multimap.get(K), Iterable<V>)
Multimap支持多种强大的视图
  • asMap 将Multimap作为Map<K, Collection<V>>。返回的Map支持remove,也支持改变写入的集合。但是map不支持put或putAll。
  • entries 返回Collection<Map.Entry<K, V>>所有Multimap的关系
  • keySet 返回所有的key集合;
  • keys 返回Multiset。可以移除元素,但是不能添加;
  • values 返回所有的values。类似于Iterables.concat(listMultimap.asMap().values())
Multimap并不是一个Map

一个Multimap<K, V> 并不是一个Map<K, Collection<V>> ,尽管这样的Map能够用Multimap来实现。值得注意的不同如下:

  • Multimap.get(K) 总是返回一个非null,可能为空的集合。返回的集合允许你添加与key有关系的value;
  • 如果你想让K不在Multimap中的时候,调用get方法返回null。那么先使用asMap() 方法得到Map<K, Collection<V>> 视图。也可以使用Multimaps的静态方法asMap
  • Multimap.containsKey(K) 只有当存在K的关系,才会返回true。
  • Multimap.entries() 返回所有key与value的关系对;如果要获取key-collection,使用Multimap.asMap().entrySet()
  • Multimap.size() 将返回所有Key的关系对数量,而不是不同key对个数。使用Multimap.asMap().size() 可以获取不同key对个数;
Multimap的实现

Multimap提供了很多实现,首先创建Multimap实现的方式是通过MultimapBuilder

实现Key的行为像value的行为像
ArrayListMultimapHashMapArrayList
HashMultimapHashMapHashSet
LinkedListMultimapLinkedHashMapLinkedList
LinkedHashMultimapLinkedHashMapLinkedHashSet
TreeMultimapTreeMapTreeSet
ImmutableListMultimapImmutableMapImmutableList
ImmutableSetMultimapImmutableMapImmutableSet

这些实现中,除了不可变集合,其它的key和value都可以为空。

可以自定义自己的实现:Multimaps.newMultimap(Map, Supplier<Collection>)

(3)Bimap
Bimap介绍

把values转成keys的传统方式是维护两个独立的map,并同步更新它们。但是这是易于出错的。当map中已经存在value的时候会造成混乱,例如:

        Map<String, Integer> nameToId = Maps.newHashMap();
        Map<Integer, String> idToName = Maps.newHashMap();
        nameToId.put("Bob", 42);
        idToName.put(42, "Bob");
        // 如果 Bob 或 42 已经存在了,会怎么样?
        // 诡异的bug将会出现,如果我们忘了同步更新

一个Bimap<K, V> 是一个Map<K, V>

  • 允许我们将Bimap<K, V> 反转,通过调用inverse() 方法;
  • 确保values是唯一的,让values() 作为一个set集合;

如果你试图通过Bimap.put(key, value)映射一个key到一个已经存在的value值上,会抛出异常:

        BiMap<String, Integer> biMap = HashBiMap.create();
        biMap.put("Bob", 42);
        biMap.put("Job", 42);
/*
Exception in thread "main" java.lang.IllegalArgumentException: value already present: 42
	at com.google.common.collect.HashBiMap.put(HashBiMap.java:310)
	at com.google.common.collect.HashBiMap.put(HashBiMap.java:290)
	at com.hef.guava.collection.GuavaCollectionDemo.biMapTest(GuavaCollectionDemo.java:32)
	at com.hef.guava.collection.GuavaCollectionDemo.main(GuavaCollectionDemo.java:19)
*/

如果你想删除预先已经存在的值,可以通过bimap.forcePut(key, value)

        BiMap<String, Integer> biMap = HashBiMap.create();
        biMap.put("Bob", 42);
        biMap.forcePut("Job", 42);
        System.out.println(biMap); // {Job=42}
Bimap的具体实现
key-value map双向map
HashMapHashBimap
ImmutableMapImmutableBimap
EnumMapEnumBimap
EnumMapEnumHashBimap
(4)Table
Table的使用

类似一个二维表,由“行、列、值”三元素组成。这里的行和列,可以是任意类型。

通常,当你想要一次索引多个keys时,你可能会想起使用Map<FirstName,Map<LastName,Person>>。Guava提供了一种新的集合类型Table,它支持使用任何的行列,以展示不同的视图:

  • rowMap() 对于Table<R, C, V>,将返回Map<R, Map<C, V>>
  • 同样的rowKeySet() 将返回Set<R>
  • row(r) 返回非null的Map<C, V>,向这个返回值的Map中写数据,值将写入底层的Table中;
  • 类似的类方法被提供:columnMap()columnKeySet()column(c)
  • cellSet() 将返回Set<Table.Cell<R, C, V>> , 类似于Map.Entry;
Table的实现
  • HashBasedTable, 它本质上是基于HashMap<R, HashMap<C, V>>
  • TreeBasedTable, 它本质上是基于TreeMap<R, TreeMap<C, V>>
  • ImmutableTable
  • ArrayTable 需要在创建的时候指定行和列。能够提升速度和内存使用率;
(5)ClassToInstanceMap

有些使用想将不同的类型作为key,该类型的值作为value。就可以使用ClassToInstanceMap。从技术上说ClassToInstanceMap实现了Map<Class<? extends B>, B>,换句话说,它是一个B类型到B类型值的映射。

ClassToInstanceMap有一个带有一个参数的构造,通过这个参数可以确定ClassToInstanceMap中B的上界类型。

除了扩展Map接口,ClassToInstanceMap还提供了两个方法:

  • T getInstance(Class<T>) 获取某个类型的值;
  • putInstance(Class<T>, T) 设置某个类型的值;

Guava提供了两个有用的ClassToInstanceMap实现:

  • MutableClassToInstanceMap
  • ImmutableClassToInstanceMap
(6)RangeSet
RangeSet 介绍

RangeSet描述了一个没有关联的非空的范围。当添加一个范围到可变集合RangeSet到时候,任何有联系的范围将合并在一块,空的范围将被忽略。例如:

        TreeRangeSet<Integer> rangeSet = TreeRangeSet.create();
        rangeSet.add(Range.closed(1, 10));
        System.out.println(rangeSet);  // [[1..10]]
        rangeSet.add(Range.closedOpen(11, 15));
        System.out.println(rangeSet);  // [[1..10], [11..15)]
        rangeSet.add(Range.closedOpen(15, 20));
        System.out.println(rangeSet);// [[1..10], [11..20)]
        rangeSet.add(Range.openClosed(0, 0));
        System.out.println(rangeSet); // [[1..10], [11..20)]
        rangeSet.remove(Range.open(5, 10));
        System.out.println(rangeSet); // [[1..5], [10..10], [11..20)]
RangeSet视图
  • complement() 展示rangeSet的补集

            TreeRangeSet<Integer> rangeSet = TreeRangeSet.create();
            rangeSet.add(Range.openClosed(2, 5));
            RangeSet<Integer> complement = rangeSet.complement();
            System.out.println(rangeSet);  // [(2..5]]
            System.out.println(complement); // [(-∞..2], (5..+∞)]
    
  • subRangeSet(Range) 获取特定范围内的子集

    RangeSet<Integer> subRangeSet = rangeSet.subRangeSet(Range.closed(4, 5));
    
  • asRanges() 返回一个Set<Range> ,用于迭代所有子集

  • asSet() 仅用于不可变集合。

查询操作
  • contains(C) 查询一个元素是否在RangSet的范围中
  • rangeContaining(C) 返回包含某一个元素的范围
  • encloses(Range) 测试一个范围是否在RangeSet中
  • span() 返回包含RangeSet所有范围的最小范围
(7)RangeMap
RangeMap 介绍

范围到值的映射。RangeMap 不会合并两个相同的范围,即便两个相邻的范围映射到同一个值。

        TreeRangeMap<Integer, String> rangeMap = TreeRangeMap.create();
        rangeMap.put(Range.closed(1, 10), "foo");
        System.out.println(rangeMap); // [[1..10]=foo]
        rangeMap.put(Range.open(3, 6), "bar");
        System.out.println(rangeMap); // [[1..3]=foo, (3..6)=bar, [6..10]=foo]
        rangeMap.put(Range.open(10, 20), "foo");
        System.out.println(rangeMap); // [[1..3]=foo, (3..6)=bar, [6..10]=foo, (10..20)=foo]
        rangeMap.remove(Range.closed(5, 11));
        System.out.println(rangeMap); // [[1..3]=foo, (3..5)=bar, (11..20)=foo]
视图
  • asMapOfRanges 返回Map<Range, V> 用于遍历RangeMap
  • subRangeMap(Range) 返回子的RangeMap

3.3 强大的集合工具

Guava提供了很多工具。

(1)集合工具类(辅助类)与接口的对应
接口JDK还是Guava对应Guava的工具类
CollectionJDKCollections2
ListJDKLists
SetJDKSets
SortedSetJDKSets
MapJDKMaps
SortedMapJDKMaps
QueueJDKQueues
MultisetGuavaMultisets
MultimapGuavaMultimaps
BiMapGuavaMaps
TableGuavaTables
(2)静态的方式构造集合对象

在JDK7之前,构建一个泛型集合需要写令人不愉快的代码:

List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<TypeThatsTooLongForItsOwnGood>();

JDK7 的菱形运算符减少了这方面的麻烦:

List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<>();

Guava 提供了静态方法,通过泛形推断右侧的类型:

ArrayList<TypeThatsTooLongForItsOwnGood> list02 = Lists.newArrayList();

在创建集合的时候,可以非常方便的初始化集合元素:

ArrayList<String> list03 = Lists.newArrayList("aa", "bb", "cc");

通过静态工厂方法,初始化集合的大小:

// 明确值
ArrayList<String> list04 = Lists.newArrayListWithCapacity(10);
// 估计值
ArrayList<String> list05 = Lists.newArrayListWithExpectedSize(10);
HashSet<Integer> set = Sets.newHashSetWithExpectedSize(10);
(3)Iterables
Iterables 的特点:惰性

只要有可能,Guava更喜欢提供接受Iterable而不是Collection的程序。在Google里,一个集合实际上并没有存在主存中的现象是不稀奇的,这个集合来自数据库或来自其他的数据中心。因为没有抓取到所有元素,所以并不支持size() 操作。

因此,你希望看到的所有集合操作都能在Iterables中找到。Iterables的大多方法都支持接受一个Iterator的版本。

在Iterables类中它的绝大多数方法都是懒惰的:只有当绝对需要迭代的时候,迭代操作发生。返回Iterables的方法,返回的是一个懒惰的计算视图,而不是构建一个明确的集合。

Iterables常用的静态方法
  • concat(Iterable...) 连接多个Iterables,返回一个惰性视图;

  • frequency 返回元素在集合中出现的频次

    对比Collections.frequency(Collection, o) 。Multiset

  • partition(Iterable, int) 返回一个不可修改的Iterable,里面是分成指定大小的块。

  • paddedPartition(Iterable, int) 分成指定大小的块,如果某一块的元素个数不足,用null补充。

  • getFirst(Iterable, T default) 返回第一个元素,没有Iterable为空,返回默认值;

  • getLast(Iterable) 返回最后一个元素,如果为空抛出异常

  • getLast(Iterable, T default) 返回最后一个元素,如果没有返回默认值

  • elementsEqual(Iterable, Iterable) 如果两个Iterable有相同的元素,则返回true

  • unmodifiableIterable(Iterable) 返回一个不可修改的视图

  • limit(Iterable, int) 返回集合的前几个元素

  • getOnlyElement(Iterable) 返回Iterable中仅有的一个元素。如果Iterable中有多个元素,将会报错

  • getOnlyElement(Iterable, T default) 返回Iterable中仅有的一个元素,如果Iterable为空,返回默认值

类集合操作

一些方法在Iterable中不支持,但在Collection中支持。当向Iterables中下面的这些方法传递Collection的时候,实际的操作会调用Collection接口的方法:

  • addAll(Collection, Iterable)

  • contains(Iterable, o)

  • removeAll(Iterable, Collection)

  • retainAll(Iterable removeFrom, Collection retain)

  • size(Iterable)

  • toArray(Iterable, Class<T>)

  • isEmpty(Iterable)

  • get(Iterable, int) 类似List.get(i)

  • toString(Iterable)

(4)Lists
  • partition(List, int) 将list切分成若干个大小为size的小块。
  • reverse(List) 反转list中的元素

静态工厂方法:

  • Lists.newLinkedList()
  • Lists.newArrayList()
(5)比较

查找最小值、最大值。一个看似很简单,却因为考虑最小的分配、装箱、API的灵活性而变得复杂。

要对比的元素刚好两个元素超过两个元素
不需要包装箱的基本数据类型Math(a, b)Ints.max(a, b, c);
Comparable类型的实例Comparators.max(a, b)Collections.max(Arrays.asList(a, b, c))
使用自定义的ComparatorComparators.max(a, b, comparator)Collections.max(Arrays.asList(a, b, c), comparator)
        Comparators.max("aa", "bb", Comparator.comparingInt(String::length));
        Collections.max(Arrays.asList("aa", "bb"),Comparator.comparingInt(String::length));
(6)Sets
SetView
  • union(set, set) 获取两个set的并集
  • intersection(set, set) 获取两个set的交集
  • difference(Set, Set) 返回前一个集合与后一个集合不同的元素
  • symmetricDifference(Set, Set) 差集,除去两个集合共有的元素
        HashSet<String> set01 = Sets.newHashSet("aa", "bb", "cc");
        HashSet<String> set02 = Sets.newHashSet("ee", "bb", "ff");
        Sets.SetView<String> result = Sets.union(set01, set02);
        System.out.println(result);
        Sets.SetView<String> intersection = Sets.intersection(set01, set02);
        System.out.println(intersection);
        Sets.SetView<String> difference = Sets.difference(set01, set02);
        System.out.println(difference);
        Sets.SetView<String> difference1 = Sets.symmetricDifference(set01, set02);
        System.out.println(difference1);

这些操作返回的是SetView

  • 可以直接把SetView当作Set使用,因为它实现了Set接口;
  • copyInto(Set) 将SetView中的元素拷贝到一个Set中;
  • immutableCopy() 拷贝成一个不可变集合
其它的操作
  • cartesianProduct(Set...) 多个集合进行组合,形成笛卡尔积
  • Sets.powerSet(Set) 获取元素的子集
HashSet<String> set01 = Sets.newHashSet("aa", "bb", "cc");
Set<Set<String>> sets = Sets.powerSet(set01);
// {{}, {"aa"}, {"bb"}, {"cc"}, {"aa", "bb"}, {"aa", "cc"}, {"bb", "cc"}, {"aa", "bb", "cc"}}
静态工厂
  • newHashSet()
  • newTreeSet()
  • newLinkedHashSet()
(7)Maps

Maps中有很多酷的方法,值得分别解释:

Maps.uniqueIndex(Iterable, Function)

解决有一堆对象(Iterable),这一些对象都有唯一的属性。想要根据属性查到对象。

// 假设这些字符串的长度是唯一的,想要根据长度查找相应的字符串
// 一旦这个唯一的属性不唯一就会报错
ArrayList<String> list = Lists.newArrayList("a", "bb", "ccc", "dddd", "eeeee", "ffffff");
ImmutableMap<Integer, String> lenMap = Maps.uniqueIndex(list, String::length);
String item = lenMap.get(5);
System.out.println(item);
Maps.difference(Map, Map)

根据MapDifference的方法:

  • entriesInCommon() key和value都匹配的项目
  • entriesDiffering() key相同,value不相同的项。返回值是Map<K, MapDifference.ValueDifference<V>> 通过MapDifference.ValueDifference能看到左右两边的值;
  • entriesOnlyOnLeft() 返回key只在左边出现的元素对;
  • entriesOnlyOnRight() 返回key只在右边出现的元素对;
        Map<String, Integer> map01 = ImmutableMap.of("aa", 1, "bb", 2, "cc", 3);
        Map<String, Integer> map02 = ImmutableMap.of("aa", 2, "d", 2, "cc", 3);
        MapDifference<String, Integer> difference = Maps.difference(map01, map02);
        Map<String, Integer> entriesInCommon = difference.entriesInCommon();
        System.out.println(entriesInCommon);  // {cc=3}
        Map<String, MapDifference.ValueDifference<Integer>> entriesDiffering = difference.entriesDiffering();
        System.out.println(entriesDiffering); // {aa=(1, 2)}
        Map<String, Integer> entriesOnlyOnLeft = difference.entriesOnlyOnLeft();
        System.out.println(entriesOnlyOnLeft); // {bb=2}
        Map<String, Integer> entriesOnlyOnRight = difference.entriesOnlyOnRight();
        System.out.println(entriesOnlyOnRight); // {d=2}
BiMap工具

因为BiMap也是一个Map,所以它的工具方法也在Maps中:

  • Maps.synchronizedBiMap(BiMap) 等价的方法Collections.synchronizedMap(Map);
  • Maps.unmodifiableBiMap(BiMap) 等价的方法Collections.unmodifiableMap(Map);
静态工厂
  • Maps.newHashMap()
  • Maps.newLinkedHashMap()
  • Maps.newTreeMap()
  • Maps.newEnumMap()
  • Maps.newConcurrentMap()
  • Maps.newIdentityHashMap()
(8)Multisets

标准的Collection操作,例如containsAll,忽略了元素在Multiset的个数,仅关心元素是否在Multiset中。Multisets提供了很多方法考虑多重性操作:

  • containsOccurrences(superMultiset, subMultiset) 如果subMultiset中的所有元素都有subMultiset.count(o)<=superMultiset(o) ,则返回true。
  • removeOccurrences(superMultiset, subMultiset) 根据subMultiset中数量移除superMultise的元素。
  • retainOccurrences(superMultiset, subMultiset) 根据subMultiset中的元素个数,保留superMultiset中的元素,其余的都移除
  • intersection(superMultiset, subMultiset) 根据元素个数获取交集
        HashMultiset<String> multiset = HashMultiset.create();
        HashMultiset<String> multiset02 = HashMultiset.create();
        multiset.addAll(Arrays.asList("aa", "bb", "aa", "bb", "cc", "cc"));
        multiset02.addAll(Arrays.asList("aa", "bb", "aa", "bb", "cc", "dd"));
				Multiset<String> intersection = Multisets.intersection(multiset, multiset02);
        System.out.println(intersection); // [aa x 2, bb x 2, cc]

Multisets中的其他工具方法:

  • copyHighestCountFirst(multiset) 返回一个不可变的Multiset,并且迭代的时候元素频次最高的在前面;
  • unmodifiableMultiset(multiset) 返回一个不可修改的Multiset
  • unmodifiableSortedMultiset(SortedMultiset) 返回一个有序的不可修改的Multiset
(9)Multimaps

Multimap提供了很多常用的方法,下面分开解释:

index(Iterable, Function),返回一个不可变集合

Maps.uniqueIndex 功能类似。Multimaps.index(Iterable, Function) 是把一批对象,属性相同的分成一组。可以根据属性找到一批对象。

        ImmutableListMultimap<Integer, String> multimap1 = Multimaps.index(Lists.newArrayList("aa", "bb", "cc", "dd", "world"), String::length);
        System.out.println(multimap1);// {2=[aa, bb, cc, dd], 5=[world]}
invertFrom(Multimap toInvert, Multimap dest)

因为多个key可以对应一个value,多个value可以对应一个key

        ListMultimap<String, Integer> multimap = MultimapBuilder.hashKeys().arrayListValues().build();
        multimap.put("name", 23);
        multimap.put("name", 21);
        multimap.put("value", 21);
        System.out.println(multimap); // {name=[23, 21], value=[21]}
        ListMultimap<Integer, String> multimap2 = Multimaps.invertFrom(multimap, MultimapBuilder.hashKeys().arrayListValues().build());
        System.out.println(multimap2);  // {21=[name, value], 23=[name]}

对于ImmutableMultimap,可以直接调用invert() 方法。

forMap(Map) 将一个Map转成SetMultimap
        Map<String, Integer> map = ImmutableMap.of("aa", 1, "bb", 1, "cc", 3);
        System.out.println(map);  // {aa=1, bb=1, cc=3}
        SetMultimap<String, Integer> setMultimap = Multimaps.forMap(map);
        System.out.println(setMultimap); // {aa=[1], bb=[1], cc=[3]}
        HashMultimap<Integer, String> multimap3 = Multimaps.invertFrom(setMultimap, HashMultimap.<Integer, String>create());
        System.out.println(multimap3);// {1=[aa, bb], 3=[cc]}
包装器:Multimaps提供了传统的包装方法
Multimap类型不可修改类型同步类型自定义类型
MultimapunmodifiableMultimapsynchronizedMultimapnewMultimap
ListMultimapunmodifiableListMultimapsynchronizedListMultimapnewListMultimap
SetMultimapunmodifiableSetMultimapsynchronizedSetMultimapnewSetMultimap
SortedSetMultimapunmodifiableSortedSetMultimapsynchronizedSortedSetMultimapnewSortedSetMultimap

自定义类型,允许指定一个实现,能够在返回的Multimap中使用。

自定义类型需要提供一个Supplier方法,用于创建新的集合。

Multimap<String, Integer> multimap4 = 
  Multimaps.newMultimap(new HashMap<String, Collection<Integer>>(), Lists::newArrayList);
(10)Tables
customTable

对比newXXXMultimap(Map, Supplier) 。customTable 可以指定row、col的数据结构。

Table<String, String, Integer> table = 
                Tables.<String, String, Integer>newCustomTable(new HashMap<String, Map<String, Integer>>(), 
                        LinkedHashMap::new);
transpose 将行和列到转
        Table<String, String, Integer> table =
                Tables.<String, String, Integer>newCustomTable(new HashMap<String, Map<String, Integer>>(),
                        LinkedHashMap::new);
        table.put("name", "value", 2);
        System.out.println(table);
        Table<String, String, Integer> transpose =
                Tables.transpose(table);
        System.out.println(transpose);
包装Table
  • unmodifiableTable 转成不可修改的Table

    Table<String, String, Integer> unmodifiableTable = Tables.unmodifiableTable(table);
    
  • unmodifiableRowSortedTable 转成row排序的Table

    RowSortedTable<String, String, Integer> treeBasedTable = TreeBasedTable.create();
            treeBasedTable.put("name1", "name2", 23);
            RowSortedTable<String, String, Integer> unmodifiableRowSortedTable = Tables.unmodifiableRowSortedTable(treeBasedTable);
    

3.4 扩展工具

有些时候你需要写自己的集合扩展。或者当元素添加到集合汇总时,你想添加特定的行为;或者你想编写一个数据库支持的Iterable。Guava提供了很多工具让你做这些事情更容易。

(1)Forwarding 装饰器

对于各种集合接口。Guava提供了Forwarding抽象类简化了装饰器模式的使用。

Forwarding类定义了一个抽象方法delegate(),你应该复写它并返回被装饰的对象。其他的方法就简单的委托给装饰者。例如,ForwardingList.get(i) 是简单的实现了delegate().get(i)

(2)PeekingIterator

通过Iterators.peekingIterator(Iteraotr) 得到PeekingIterator。其中的peek() 能够得到next() 元素

        List<String> result = Lists.newArrayList("a", "b", "c");
        PeekingIterator<String> peekingIterator = Iterators.peekingIterator(result.iterator());
        // 能够打印5次 a
        for (int i = 0; i < 5; i++) {
            System.out.println(peekingIterator.peek());
        }
(3)AbstractIterator

通过AbstractIterator 可以很方便的实现自己的Iterator:

    private Iterator<String> skipNullIterator(final Iterator<String> in){
        return new AbstractIterator<String>() {
            @CheckForNull
            @Override
            protected String computeNext() {
                while (in.hasNext()) {
                    String s = in.next();
                    if (s!=null) {
                        return s;
                    }
                }
                return endOfData();
            }
        };
    }
(4)AbstractSequentialIterator
    private Iterator<Integer> sequentialIterator() {
        return new AbstractSequentialIterator<Integer>(2) {
            @CheckForNull
            @Override
            protected Integer computeNext(Integer previous) {
                return previous>100?null:previous*3;
            }
        };
    }
  • 必须要有一个初始值;
  • null作为迭代的结束;
  • AbstractSequentialIterator不能用于迭代元素包含null的实现;
  • 11
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值