文章内容
guava就是google used java,它是一个谷歌持续维护的一个核心的java工具包,囊括了从新集合类型、不可变集合、图结构处理、并发工具、I/O、哈希、缓存等等各个方面的工具。它被各个公司各大项目广泛使用,可以说,几乎有java出现的地方就有它的身影。
事实上,guava已经成了JDK以外的又一个标准,guava里涵盖的很多东西,最终都被添加到了jdk中,如函数式编程、Stream流、Optional类、不可变集合API等等。
无论从哪个角度,guava都是值得java程序员深入学习的一个工具库。
guava的思维导图:

1、集合框架
1.1、集合工具类
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | // 创建一个ArrayList,可以传不定长参数 List<Integer> arrayList = Lists.newArrayList( 1 , 2 , 3 ); // 创建一个LinkedList,传一个iterable(可迭代)变量 List<Integer> linkedList = Lists.newLinkedList(arrayList); // 翻转一个集合,原集合不变 List<Integer> reversed = Lists.reverse(arrayList); // 集合变换,传一个function对象,相当于stream操作 List<Integer> transformed = Lists.transform(arrayList, i -> i * 2 ); // 创建一个HashSet Set<Integer> hashSet = Sets.newHashSet( 1 , 2 , 3 ); // 创建一个TreeSet Set<Integer> treeSet = Sets.newTreeSet(Sets.newHashSet( 4 , 2 , 3 )); // 求两个Set的并集,返回的是一个只读的视图 Sets.SetView<Integer> union = Sets.union(hashSet, treeSet); // 求两个Set的交集,返回的是一个只读的视图 Sets.SetView<Integer> intersection = Sets.intersection(hashSet, treeSet); // 求两个Set的差集,返回的是一个只读的视图 Sets.SetView<Integer> difference = Sets.difference(hashSet, treeSet); // jdk9创建不可变map的api Map<String, Object> map0 = Map.of( "a" , 1 , "b" , 2 , "c" , 3 ); Map<String, Object> map1 = Map.of( "a" , 1 , "b" , 2 , "d" , 4 ); // 求两个map的差异 MapDifference<String, Object> mapDifference = Maps.difference(map0, map1); |
1.2、扩展集合
主要扩展了Multiset(背包)和Multimap(多值映射)
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // Multiset是支持重复元素的集合,即背包 // 创建背包 Multiset<String> multiset = HashMultiset.create(); // 向背包中添加一个元素 multiset.add( "one" ); // 向背包中添加多个相同元素 multiset.add( "two" , 4 ); // 查询元素个数 int oneCount = multiset.count( "one" ); // 删除一个元素若干次 multiset.remove( "two" , 2 ); // Multimap是一个key对应多个value的map // 创建一个multimap Multimap<String, Object> multimap = HashMultimap.create(); // 可以在相同的key下put值,put后map元素为{a=[1, 2]} multimap.put( "a" , 1 ); multimap.put( "a" , 2 ); // 可以通过putAll为一个key一次性放置多个value,value不保证有序 multimap.putAll( "b" , Sets.newHashSet( 3 , 4 , 5 )); |
1.3、不可变集合
可以创建不可变集合,这样可以保证集合中的元素不发生变化。如果使用的jdk是9以上版本,也可以直接使用jdk自带的api。
01 02 03 04 05 06 07 08 09 10 11 | // 创建不可变list,上面为guava api,下面为jdk9 api List<Integer> immutableList = ImmutableList.of( 1 , 2 , 3 ); List<Integer> jdkImmutableList = List.of( 1 , 2 , 3 ); // 创建不可变set,上面为guava api,下面为jdk9 api Set<Integer> immutableSet = ImmutableSet.of( 1 , 2 , 3 ); Set<Integer> jdkImmutableSet = Set.of( 1 , 2 , 3 ); // 创建不可变map,上面为guava api,下面为jdk9 api Map<String, Integer> immutableMap = ImmutableMap.of( "a" , 1 , "b" , 2 , "c" , 3 ); Map<String, Integer> jdkImmutableMap = Map.of( "a" , 1 , "b" , 2 , "c" , 3 ); |
1.4、集合排序
jdk8之后,Ordering类已废弃,建议使用jdk8的Stream和Comparator。
1.5、流式处理
jdk8之后,FluentIterable类已废弃,建议使用jdk8的Stream。
2、图结构处理
图是由边和顶点组成的数据结构,边将各个顶点进行联结。guava的graph包就是为了模型化图这种数据结构而创建的。
graph包中提供了三种主要的graph类型,即Graph、ValueGraph和Network。
Graph是最基本的图,它的边仅仅是为了联结顶点,本身没有标识或属性。
ValueGraph的边有自己的值(或者说权重),当你需要关注边的信息时可以使用该类型。
Network指一对节点间不止一个边联结,并且这些边可以通过唯一标识进行区分。
下面给出一些常用的方法:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | // 创建一个可变的无向图 MutableGraph<Integer> graph = GraphBuilder.undirected().build(); // 创建一个可变的有向值图 MutableValueGraph<City, Distance> roads = ValueGraphBuilder.directed() .incidentEdgeOrder(ElementOrder.stable()) .build(); // 创建一个可变的有向网络 MutableNetwork<Webpage, Link> webSnapshot = NetworkBuilder.directed() .allowsParallelEdges( true ) .nodeOrder(ElementOrder.natural()) .expectedNodeCount( 100000 ) .expectedEdgeCount( 1000000 ) .build(); // 创建一个不可变的图 ImmutableGraph<Country> countryAdjacencyGraph = GraphBuilder.undirected() .<Country>immutable() .putEdge(FRANCE, GERMANY) .putEdge(FRANCE, BELGIUM) .putEdge(GERMANY, BELGIUM) .addNode(ICELAND) .build(); // 有向图/无向图添加边 directedGraph.addEdge(nodeU, nodeV, edgeUV_a); directedGraph.addEdge(nodeU, nodeV, edgeUV_b); directedGraph.addEdge(nodeV, nodeU, edgeVU); undirectedGraph.addEdge(nodeU, nodeV, edgeUV_a); undirectedGraph.addEdge(nodeU, nodeV, edgeUV_b); undirectedGraph.addEdge(nodeV, nodeU, edgeVU); |
3、并发工具
3.1、ListenableFuture
ListenableFuture是jdk的Future接口的一个扩展,guava团队建议在所有使用Future的地方,使用ListenableFuture进行代替。
ListenableFuture增加了Future执行成功或失败的监听器,可以在计算完成时即时进行回调,使得并发编程的代码更加高效。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // 使用listeningDecorator方法包装线程池 ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool( 10 )); // 调用submit方法,即可返回ListenableFuture ListenableFuture<Explosion> explosion = service.submit( new Callable<Explosion>() { public Explosion call() { return pushBigRedButton(); } }); // 通过Futures工具类的addCallback方法,为ListenableFuture添加执行成功和失败的回调 Futures.addCallback( explosion, new FutureCallback<Explosion>() { // 执行成功 public void onSuccess(Explosion explosion) { walkAwayFrom(explosion); } // 执行失败 public void onFailure(Throwable thrown) { battleArchNemesis(); } }, service); |
3.2、Service
Service接口代表了对象的运行状态,它提供了开始和停止的方法。计时器、RPC服务器等服务均可以实现此接口,以便管理开启和结束等状态。
Service提供了五个正常的状态和一个失败的状态,即:
- Service.State.NEW 新建
- Service.State.STARTING 开启
- Service.State.RUNNING 运行中
- Service.State.STOPPING 正在停止
- Service.State.TERMINATED 中止
- Service.State.FAILED 失败
正常服务的生命周期应该为:
新建->开启->运行中->停止->中止
4、I/O
封装了一些更方便的I/O工具。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 | // 将整个输入流读为byte数组,不会关闭流 byte [] bytes = ByteStreams.toByteArray(inputStream); // 将整个输入流中的字节复制到输出流中,不会关闭流或清空缓存区 long length = ByteStreams.copy(inputStream, outputStream); // 将整个输入流/可读对象中的数据按行读出,不包含换行符 List<String> lines = CharStreams.readLines( new InputStreamReader(inputStream)); // 将整个输入流/可读对象中的数据读成一个字符串,不关闭流 String s = CharStreams.toString( new InputStreamReader(inputStream)); // 读取一个文本文件所有行 ImmutableList<String> textLines = Files.asCharSource(file, Charsets.UTF_8) .readLines(); // 计算一个文件里出现的不同单词的次数 Multiset<String> wordOccurrences = HashMultiset.create( Splitter.on(CharMatcher.whitespace()) .trimResults() .omitEmptyStrings() .split(Files.asCharSource(file, Charsets.UTF_8).read())); |
5、哈希
提供更复杂的哈希算法,包括Bloom filter数据结构,该结构通过允许少量的错误来节省大量的存储空间。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | // 要计算hashCode的实体类 class Person { int id; String firstName; String lastName; int birthYear; } // 将对象分解为基础属性值的数据漏斗 Funnel<Person> personFunnel = (person, into) -> into .putInt(person.id) .putString(person.firstName, Charsets.UTF_8) .putString(person.lastName, Charsets.UTF_8) .putInt(person.birthYear); // 初始化哈希函数 HashFunction hf = Hashing.sha256(); // 计算哈希值 HashCode hc = hf.newHasher() .putLong(id) .putString(name, Charsets.UTF_8) .putObject(person, personFunnel) .hash(); // 创建BloomFilter BloomFilter<Person> friends = BloomFilter.create(personFunnel, 500 , 0.01 ); // 向Bloom Filter对象中添加元素 for (Person friend : friendsList) { friends.put(friend); } // 判断Bloom Filter里是否可能包含某个元素,这里牺牲一定的准确度来换取空间和执行效率 if (friends.mightContain(dude)) { // 如果不包含元素并且代码执行到这里的概率是1%。 } |
6、缓存
本地缓存(内存级别的缓存),支持各种方式的过期行为,不会将数据存到硬盘。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 | // 创建缓存 LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder() .maximumSize( 1000 ) .build( new CacheLoader<>() { @Override public Graph load(Key key) throws Exception { // 通过key来生成值,这个过程消耗资源比较大,因此需要缓存 return createExpensiveGraph(key); } }); // 读取缓存中的值 try { // 尝试获取缓存中的值,如果没有值,通过CacheLoader执行load方法添加值 graphs.get(key); } catch (ExecutionException e) { // 异常处理 } |
7、事件总线
使组件间保持“发布-订阅”风格的通信,不需要将组件互相注册。它是为了替换使用显式注册发布事件的方式,而非通用的“发布-订阅”系统。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 | // 事件处理器 class EventBusChangeRecorder { @Subscribe public void recordCustomerChange(ChangeEvent e) { recordChange(e.getChange()); } } // 订阅事件 EventBus eventBus = new EventBus( "event" ); eventBus.register( new EventBusChangeRecorder()); // 创建事件 ChangeEvent e = getChangeEvent(); // 发布事件 eventBus.post(e); |
8、基础工具
8.1、空值处理
jdk8之后,Optional类已废弃,建议使用jdk8同名的Optional。
8.2、前置条件
可以检查参数是否合法、状态是否正确、是否为空值。
1 2 3 4 5 6 7 8 | // 参数检查,检查不通过抛出IllegalArgumentException Preconditions.checkArgument(age >= 18 && age <= 70 , "年龄应该在%s和%s之间" , 18 , 70 ); // 状态检查,检查不通过抛出IllegalStatementException Preconditions.checkState(state == OK, "当前状态不正确,应该为%s" , "OK" ); // 空值检查,检查通过返回原值,不通过抛出NullPointerException nonNull = Preconditions.checkNotNull(nonNull, "当前值不可以为空" ); |
8.3、反射工具
提供更方便的反射工具。
1 2 3 4 5 | // 获取类的包名 final String packageName = Reflection.getPackageName(Test. class ); // 生成一个接口的动态代理对象 Foo foo = Reflection.newProxy(Foo. class , invocationHandler); |
8.4、数学库
提供了一些JDK没有提供的数学工具,这些工具已经经过了彻底的测试。数学库中的函数很多,下面是一些简单的例子:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 | // 保证相加的结果不溢出,如果溢出则抛出ArithmeticException int sum = IntMath.checkedAdd(a, b); // 获取两个整数的最大公约数 int gcd = IntMath.gcd(a, b); // 比较两个浮点数是否在容差范围内相等 final boolean equals = DoubleMath.fuzzyEquals( 1.0 / 3 , 0.3333333 , 0.00001 ); // 保证幂运算的结果不溢出,如果溢出则抛出ArithmeticException final long checkedPow = LongMath.checkedPow( 5 , 20 ); // 将BigDecimal数字近似为double,如果数字过大,则返回Double.MAX_VALUE。 final double v = BigDecimalMath.roundToDouble(BigDecimal.valueOf( 2 ).pow( 2000 ), HALF_UP); |
9、依赖引入
guava库区分jre版本(用于8.0及以上版本JDK)和android版本(用于android平台和7.0以下版本的JDK)。在项目中可以通过maven引入guava库,方式如下:
1 2 3 4 5 6 7 | < dependency > < groupId >com.google.guava</ groupId > < artifactId >guava</ artifactId > < version >30.1.1-jre</ version > <!-- 如果是Android版本,则用下面的version标签: --> < version >30.1.1-android</ version > </ dependency > |
10、注意事项
1、部分功能已加入到JDK中,可以优先使用JDK中的相同功能
这些功能包括:
- 用于空值处理Optional的类
- 基础的函数式接口,如Function、Predicate、Supplier
- 流式计算,FluentIterable的操作相当于JDK8中的Stream操作。
- I/O操作,Files工具类
- 不可变集合,jdk中有ImmutableCollections工具类