最近在看一个同学代码的时候,发现代码中大量使用了 Google 开源的 Guava 核心库中的内容,让代码简单清晰了不少,故学习分享出 Guava 中我认为最实用的功能。

Guava 项目是 Google 公司开源的 Java 核心库,它主要是包含一些在 Java 开发中经常使用到的功能,如数据校验不可变集合、计数集合,集合增强操作、I/O、缓存、字符串操作等。并且 Guava 广泛用于 Google 内部的 Java 项目中,也被其他公司广泛使用,甚至在新版 JDK 中直接引入了 Guava 中的优秀类库,所以质量毋庸置疑。

使用方式直接 mavan 依赖引入。

  1. <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
  2. <dependency>
  3. <groupId>com.google.guava</groupId>
  4. <artifactId>guava</artifactId>
  5. <version>30.0-jre</version>
  6. </dependency>

数据校验

数据校验说来十分简单,一是非空判断,二是预期值判断。非空判断我想每一个 Java 开发者都很熟悉,一开始都经常和 NullPointException 打交道。处理的方式我们自然是一个 if( xx == null) 就能轻松解决。预期值判断也是类似,检查数据值是不是自己想要的结果即可。

即使这么简单的操作,我们是不是还经常出错呢?而且写起来的代码总是一行判断一行异常抛出,怎么看都觉得那么优雅。还好,现在就来尝试第一次使用 Guava 吧。

非空判断

  1. String param = "未读代码";
  2. String name = Preconditions.checkNotNull(param);
  3. System.out.println(name); // 未读代码
  4. String param2 = null;
  5. String name2 = Preconditions.checkNotNull(param2); // NullPointerException
  6. System.out.println(name2);

引入了 Guava 后可以直接使用 Preconditions.checkNotNull 进行非空判断,好处为觉得有两个,一是语义清晰代码优雅;二是你也可以自定义报错信息,这样如果参数为空,报错的信息清晰,可以直接定位到具体参数。

  1. String param2 = null;
  2. String name2 = Preconditions.checkNotNull(param2,"param2 is null");
  3. // java.lang.NullPointerException: param2 is null

预期值判断

和非空判断类似,可以比较当前值和预期值,如果不相等可以自定义报错信息抛出。

  1. String param = "www.wdbyte.com2";
  2. String wdbyte = "www.wdbyte.com";
  3. Preconditions.checkArgument(wdbyte.equals(param), "[%s] 404 NOT FOUND", param);
  4. // java.lang.IllegalArgumentException: [www.wdbyte.com2] 404 NOT FOUND

是否越界

Preconditions 类还可以用来检查数组和集合的元素获取是否越界。

  1. // Guava 中快速创建ArrayList
  2. List<String> list = Lists.newArrayList("a", "b", "c", "d");
  3. // 开始校验
  4. int index = Preconditions.checkElementIndex(5, list.size());
  5. // java.lang.IndexOutOfBoundsException: index (5) must be less than size (4)

代码中快速创建 List 的方式也是 Guava 提供的,后面会详细介绍 Guava 中集合创建的超多姿势。

不可变的集合

创建不可变集合是我个人最喜欢 Guava 的一个原因,因为创建一个不能删除、不能修改、不能增加元素的集合实在是太实用了。这样的集合你完全不用担心发生什么问题,总的来说有下面几个优点:

  1. 线程安全,因为不能修改任何元素,可以随意多线程使用且没有并发问题。
  2. 可以无忧的提供给第三方使用,反正修改不了。
  3. 减少内存占用,因为不能改变,所以内部实现可以最大程度节约内存占用。
  4. 可以用作常量集合。

创建方式

说了那么多,那么到底怎么使用呢?赶紧撸起代码来。

  1. // 创建方式1:of
  2. ImmutableSet<String> immutableSet = ImmutableSet.of("a", "b", "c");
  3. immutableSet.forEach(System.out::println);
  4. // a
  5. // b
  6. // c
  7. // 创建方式2:builder
  8. ImmutableSet<String> immutableSet2 = ImmutableSet.<String>builder()
  9. .add("hello")
  10. .add(new String("未读代码"))
  11. .build();
  12. immutableSet2.forEach(System.out::println);
  13. // hello
  14. // 未读代码
  15. // 创建方式3:从其他集合中拷贝创建
  16. ArrayList<String> arrayList = new ArrayList();
  17. arrayList.add("www.wdbyte.com");
  18. arrayList.add("https");
  19. ImmutableSet<String> immutableSet3 = ImmutableSet.copyOf(arrayList);
  20. immutableSet3.forEach(System.out::println);
  21. // www.wdbyte.com
  22. // https

都可以正常打印遍历结果,但是如果进行增删改,会直接报 UnsupportedOperationException .

其实 JDK 中也提供了一个不可变集合,可以像下面这样创建。

  1. ArrayList<String> arrayList = new ArrayList();
  2. arrayList.add("www.wdbyte.com");
  3. arrayList.add("https");
  4. // JDK Collections 创建不可变 List
  5. List<String> list = Collections.unmodifiableList(arrayList);
  6. list.forEach(System.out::println);// www.wdbyte.com https
  7. list.add("未读代码"); // java.lang.UnsupportedOperationException

注意事项

  1. 使用 Guava 创建的不可变集合是拒绝 null 值的,因为在 Google 内部调查中,95% 的情况下都不需要放入 null 值。

  2. 使用 JDK 提供的不可变集合创建成功后,原集合添加元素会体现在不可变集合中,而 Guava 的不可变集合不会有这个问题。

    1. List<String> arrayList = new ArrayList<>();
    2. arrayList.add("a");
    3. arrayList.add("b");
    4. List<String> jdkList = Collections.unmodifiableList(arrayList);
    5. ImmutableList<String> immutableList = ImmutableList.copyOf(arrayList);
    6. arrayList.add("ccc");
    7. jdkList.forEach(System.out::println);// result: a b ccc
    8. System.out.println("-------");
    9. immutableList.forEach(System.out::println);// result: a b
  3. 如果不可变集合的元素是引用对象,那么引用对象的属性是可以更改的。

其他不可变集合

不可变集合除了上面演示的 set 之外,还有很多不可变集合,下面是 Guava 中不可变集合和其他集合的对应关系。

可变集合接口 属于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

集合操作工厂

其实这里只会介绍一个创建方法,但是为什么还是单独拿出来介绍了呢?看下去你就会大呼好用。虽然 JDK 中已经提供了大量的集合相关的操作方法,用起来也是非常的方便,但是 Guava 还是增加了一些十分好用的方法,保证让你用上一次就爱不释手,

创建集合。

  1. // 创建一个 ArrayList 集合
  2. List<String> list1 = Lists.newArrayList();
  3. // 创建一个 ArrayList 集合,同时塞入3个数据
  4. List<String> list2 = Lists.newArrayList("a", "b", "c");
  5. // 创建一个 ArrayList 集合,容量初始化为10
  6. List<String> list3 = Lists.newArrayListWithCapacity(10);
  7. LinkedList<String> linkedList1 = Lists.newLinkedList();
  8. CopyOnWriteArrayList<String> cowArrayList = Lists.newCopyOnWriteArrayList();
  9. HashMap<Object, Object> hashMap = Maps.newHashMap();
  10. ConcurrentMap<Object, Object> concurrentMap = Maps.newConcurrentMap();
  11. TreeMap<Comparable, Object> treeMap = Maps.newTreeMap();
  12. HashSet<Object> hashSet = Sets.newHashSet();
  13. HashSet<String> newHashSet = Sets.newHashSet("a", "a", "b", "c");

Guava 为每一个集合都添加了工厂方法创建方式,上面已经展示了部分集合的工厂方法创建方式。是不是十分的好用呢。而且可以在创建时直接扔进去几个元素,这个简直太赞了,再也不用一个个 add 了。

集合交集并集差集

过于简单,直接看代码和输出结果吧。

  1. Set<String> newHashSet1 = Sets.newHashSet("a", "a", "b", "c");
  2. Set<String> newHashSet2 = Sets.newHashSet("b", "b", "c", "d");
  3. // 交集
  4. SetView<String> intersectionSet = Sets.intersection(newHashSet1, newHashSet2);
  5. System.out.println(intersectionSet); // [b, c]
  6. // 并集
  7. SetView<String> unionSet = Sets.union(newHashSet1, newHashSet2);
  8. System.out.println(unionSet); // [a, b, c, d]
  9. // newHashSet1 中存在,newHashSet2 中不存在
  10. SetView<String> setView = Sets.difference(newHashSet1, newHashSet2);
  11. System.out.println(setView); // [a]

有数量的集合

这个真的太有用了,因为我们经常会需要设计可以计数的集合,或者 value 是 ListMap 集合,如果说你不太明白,看下面这段代码,是否某天夜里你也这样写过。

  1. 统计相同元素出现的次数(下面的代码我已经尽可能精简写法了)。

    JDK 原生写法:

    1. // Java 统计相同元素出现的次数。
    2. List<String> words = Lists.newArrayList("a", "b", "c", "d", "a", "c");
    3. Map<String, Integer> countMap = new HashMap<String, Integer>();
    4. for (String word : words) {
    5. Integer count = countMap.get(word);
    6. count = (count == null) ? 1 : ++count;
    7. countMap.put(word, count);
    8. }
    9. countMap.forEach((k, v) -> System.out.println(k + ":" + v));
    10. /**
    11. * result:
    12. * a:2
    13. * b:1
    14. * c:2
    15. * d:1
    16. */

    尽管已经尽量优化代码,代码量还是不少的,那么在 Guava 中有什么不一样呢?在 Guava. 中主要是使用 HashMultiset 类,看下面。

    1. ArrayList<String> arrayList = Lists.newArrayList("a", "b", "c", "d", "a", "c");
    2. HashMultiset<String> multiset = HashMultiset.create(arrayList);
    3. multiset.elementSet().forEach(s -> System.out.println(s + ":" + multiset.count(s)));
    4. /**
    5. * result:
    6. * a:2
    7. * b:1
    8. * c:2
    9. * d:1
    10. */

    是的,只要把元素添加进去就行了,不用在乎是否重复,最后都可以使用 count 方法统计重复元素数量。看着舒服,写着优雅,HashMultiset 是 Guava 中实现的 Collection 类,可以轻松统计元素数量。

  2. 一对多,value 是 ListMap 集合。

    假设一个场景,需要把很多动物按照种类进行分类,我相信最后你会写出类似的代码。

    JDK 原生写法:

    1. HashMap<String, Set<String>> animalMap = new HashMap<>();
    2. HashSet<String> dogSet = new HashSet<>();
    3. dogSet.add("旺财");
    4. dogSet.add("大黄");
    5. animalMap.put("狗", dogSet);
    6. HashSet<String> catSet = new HashSet<>();
    7. catSet.add("加菲");
    8. catSet.add("汤姆");
    9. animalMap.put("猫", catSet);
    10. System.out.println(animalMap.get("猫")); // [加菲, 汤姆]

    最后一行查询猫得到了猫类的 "加菲" 和 ”汤姆“。这个代码简直太烦做了,如果使用 Guava 呢?

    1. // use guava
    2. HashMultimap<String, String> multimap = HashMultimap.create();
    3. multimap.put("狗", "大黄");
    4. multimap.put("狗", "旺财");
    5. multimap.put("猫", "加菲");
    6. multimap.put("猫", "汤姆");
    7. System.out.println(multimap.get("猫")); // [加菲, 汤姆]

    HashMultimap 可以扔进去重复的 key 值,最后获取时可以得到所有的 value 值,可以看到输出结果和 JDK 写法上是一样的,但是代码已经无比清爽。

字符串操作

作为开发中最长使用的数据类型,字符串操作的增强可以让开发更加高效。

字符拼接

JDK 8 中其实已经内置了字符串拼接方法,但是它只是简单的拼接,没有额外操作,比如过滤掉 null 元素,去除前后空格等。先看一下 JDK 8 中字符串拼接的几种方式。

  1. // JDK 方式一
  2. ArrayList<String> list = Lists.newArrayList("a", "b", "c", null);
  3. String join = String.join(",", list);
  4. System.out.println(join); // a,b,c,null
  5. // JDK 方式二
  6. String result = list.stream().collect(Collectors.joining(","));
  7. System.out.println(result); // a,b,c,null
  8. // JDK 方式三
  9. StringJoiner stringJoiner = new StringJoiner(",");
  10. list.forEach(stringJoiner::add);
  11. System.out.println(stringJoiner.toString()); // a,b,c,null

可以看到 null 值也被拼接到了字符串里,这有时候不是我们想要的,那么使用 Guava 有什么不一样呢?

  1. ArrayList<String> list = Lists.newArrayList("a", "b", "c", null);
  2. String join = Joiner.on(",").skipNulls().join(list);
  3. System.out.println(join); // a,b,c
  4. String join1 = Joiner.on(",").useForNull("空值").join("旺财", "汤姆", "杰瑞", null);
  5. System.out.println(join1); // 旺财,汤姆,杰瑞,空值

可以看到使用 skipNulls() 可以跳过空值,使用 useFornull(String) 可以为空值自定义显示文本。

字符串分割

JDK 中是自带字符串分割的,我想你也一定用过,那就是 String 的 split 方法,但是这个方法有一个问题,就是如果最后一个元素为空,那么就会丢弃,奇怪的是第一个元素为空却不会丢弃,这就十分迷惑,下面通过一个例子演示这个问题。

  1. String str = ",a,,b,";
  2. String[] splitArr = str.split(",");
  3. Arrays.stream(splitArr).forEach(System.out::println);
  4. System.out.println("------");
  5. /**
  6. *
  7. * a
  8. *
  9. * b
  10. * ------
  11. */

你也可以自己测试下,最后一个元素不是空,直接消失了。

如果使用 Guava 是怎样的操作方式呢?Guava 提供了 Splitter 类,并且有一系列的操作方式可以直观的控制分割逻辑。

  1. String str = ",a ,,b ,";
  2. Iterable<String> split = Splitter.on(",")
  3. .omitEmptyStrings() // 忽略空值
  4. .trimResults() // 过滤结果中的空白
  5. .split(str);
  6. split.forEach(System.out::println);
  7. /**
  8. * a
  9. * b
  10. */

缓存

在开发中我们可能需要使用小规模的缓存,来提高访问速度。这时引入专业的缓存中间件可能又觉得浪费。现在可以了, Guava 中提供了简单的缓存类,且可以根据预计容量、过期时间等自动过期已经添加的元素。即使这样我们也要预估好可能占用的内存空间,以防内存占用过多。

现在看一下在 Guava 中缓存该怎么用。

  1. @Test
  2. public void testCache() throws ExecutionException, InterruptedException {
  3. CacheLoader cacheLoader = new CacheLoader<String, Animal>() {
  4. // 如果找不到元素,会调用这里
  5. @Override
  6. public Animal load(String s) {
  7. return null;
  8. }
  9. };
  10. LoadingCache<String, Animal> loadingCache = CacheBuilder.newBuilder()
  11. .maximumSize(1000) // 容量
  12. .expireAfterWrite(3, TimeUnit.SECONDS) // 过期时间
  13. .removalListener(new MyRemovalListener()) // 失效监听器
  14. .build(cacheLoader); //
  15. loadingCache.put("狗", new Animal("旺财", 1));
  16. loadingCache.put("猫", new Animal("汤姆", 3));
  17. loadingCache.put("狼", new Animal("灰太狼", 4));
  18. loadingCache.invalidate("猫"); // 手动失效
  19. Animal animal = loadingCache.get("狼");
  20. System.out.println(animal);
  21. Thread.sleep(4 * 1000);
  22. // 狼已经自动过去,获取为 null 值报错
  23. System.out.println(loadingCache.get("狼"));
  24. /**
  25. * key=猫,value=Animal{name='汤姆', age=3},reason=EXPLICIT
  26. * Animal{name='灰太狼', age=4}
  27. * key=狗,value=Animal{name='旺财', age=1},reason=EXPIRED
  28. * key=狼,value=Animal{name='灰太狼', age=4},reason=EXPIRED
  29. *
  30. * com.google.common.cache.CacheLoader$InvalidCacheLoadException: CacheLoader returned null for key 狼.
  31. */
  32. }
  33. /**
  34. * 缓存移除监听器
  35. */
  36. class MyRemovalListener implements RemovalListener<String, Animal> {
  37. @Override
  38. public void onRemoval(RemovalNotification<String, Animal> notification) {
  39. String reason = String.format("key=%s,value=%s,reason=%s", notification.getKey(), notification.getValue(), notification.getCause());
  40. System.out.println(reason);
  41. }
  42. }
  43. class Animal {
  44. private String name;
  45. private Integer age;
  46. @Override
  47. public String toString() {
  48. return "Animal{" +
  49. "name='" + name + '\'' +
  50. ", age=" + age +
  51. '}';
  52. }
  53. public Animal(String name, Integer age) {
  54. this.name = name;
  55. this.age = age;
  56. }
  57. }

这个例子中主要分为 CacheLoader、MyRemovalListener、LoadingCache。

CacheLoader 中重写了 load 方法,这个方法会在查询缓存没有命中时被调用,我这里直接返回了 null,其实这样会在没有命中时抛出 CacheLoader returned null for key 异常信息。

MyRemovalListener 作为缓存元素失效时的监听类,在有元素缓存失效时会自动调用 onRemoval 方法,这里需要注意的是这个方法是同步方法,如果这里耗时较长,会阻塞直到处理完成。

LoadingCache 就是缓存的主要操作对象了,常用的就是其中的 putget 方法了。

总结

上面介绍了我认为最常用的 Guava 功能,Guava 作为 Google 公司开源的 Java 开发核心库,个人觉得实用性还是很高的。引入后不仅能快速的实现一些开发中常用的功能,而且还可以让代码更加的优雅简洁。我觉得适用于每一个 Java 项目。Guava 的其他的功能你也可以自己去发现。它的 Github 地址是:https://github.com/google/guava.

参考

  1. https://github.com/google/guava/wiki

订阅

文章已经收录在 Github.com/niumoo/JavaNotes ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 Star 和完善,希望我们一起变得优秀。

文章每周持续更新,有帮助可以点个「」或「分享」,都是支持,我都喜欢!

要实时关注更新的文章以及分享的干货,可以关注 未读代码 公众号(下方二维码)或者我的网站

Guava - 拯救垃圾代码,写出优雅高效,效率提升N倍的更多相关文章

  1. 如何写出优雅的JavaScript代码 ? && 注释

    如何写出优雅的JavaScript代码 ? 之前总结过一篇<如何写出优雅的css代码?>, 但是前一段时间发现自己的js代码写的真的很任性,没有任何的优雅可言,于是这里总结以下写js时应当 ...

  2. 如何写出优雅的CSS代码 ?(转)

    对于同样的项目或者是一个网页,尽管最终每个前端开发工程师都可以实现相同的效果,但是他们所写的代码一定是不同的.有的优雅,看起来清晰易懂,代码具有可拓展性,这样的代码有利于团队合作和后期的维护:而有的混 ...

  3. 如何写出优雅的css代码 ?

    如何写出优雅的css代码 ? 对于同样的项目或者是一个网页,尽管最终每个前端开发工程师都可以实现相同的效果,但是他们所写的代码一定是不同的.有的优雅,看起来清晰易懂,代码具有可拓展性,这样的代码有利于 ...

  4. 【原创】怎样才能写出优雅的 Java 代码?这篇文章告诉你答案!

    本文已经收录自 JavaGuide (59k+ Star):[Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识. 本文比较简短,基本就是推荐一些对于写好代码非常有用的文章或者 ...

  5. 如何写出优雅的 Golang 代码

    原文: https://draveness.me/golang-101.html Go 语言是一门简单.易学的编程语言,对于有编程背景的工程师来说,学习 Go 语言并写出能够运行的代码并不是一件困难的 ...

  6. 深入了解Promise对象,写出优雅的回调代码,告别回调地狱

    深入浅出了解Promise 引言 正文 一.Promise简介 二.Promise的三种状态 三.函数then( ) 四.函数catch( ) 五.函数finally( ) 六.函数all( ) 七. ...

  7. CSS代码写出的各种形状图形

    做网页设计时经常要用到各种形状的图形,对于规则的图形很简单,但是对于不规则的图形,一般我们都是用图片,今天就在这里教大家怎样用css代码写出各种规则不同的图形 1.正方形 #square {width ...

  8. 教你用CSS代码写出的各种形状图形

    做网页设计时经常要用到各种形状的图形,对于规则的图形很简单,但是对于不规则的图形,一般我们都是用图片,今天就在这里教大家怎样用css代码写出各种规则不同的图形 1.正方形 #square {width ...

  9. [置顶] 如何用PYTHON代码写出音乐

    如何用PYTHON代码写出音乐 什么是MIDI 博主本人虽然五音不全,而且唱歌还很难听,但是还是非常喜欢听歌的.我一直在做这样的尝试,就是通过人工智能算法实现机器自动的作词和编曲(在这里预告下,通过深 ...

随机推荐

  1. HYWZ 吴恩达-机器学习+神经网络反向传播

  2. 单调队列优化O(N)建BST P1377 [TJOI2011]树的序

    洛谷 P1377 [TJOI2011]树的序 (单调队列优化建BST 链接 题意分析 本题思路很简单,根据题意,我们利用所给的Bst生成序将Bst建立起来,然后输出该BST的先序遍历即可: 但,如果我 ...

  3. ios自动识别电话并变色的问题解决方法

    问题: 在做移动端页面时发现长串数字都被ios系统的手机识别为电话号码,且文字变成很土的蓝色,点击有下划线并弹出提示拨打该电话号码. 解决方法: 1.在head中加上下面这行代码就OK了(仅限于单页面 ...

  4. 开源 UI 库中,唯一同时实现了大表格虚拟化和树表格的 Table 组件

    背景 有这样一个需求,一位 React Suite(以下简称 rsuite)的用户,他需要一个 Table 组件能够像 Jira Portfolio 一样,支持树形数据,同时需要支持大数据渲染. 截止 ...

  5. spring @value和@@PropertySource注解简单使用

    @Value注解:可以使用注入基本字符串 EL表达式,从配置文件读取数据@PropertySource用于引入单个配置文件 @PropertySources用于引入多个配置文件 @PropertySo ...

  6. 《RESTful Web APIs》书中有一段POST API示例,现实中我们如何测试这个示例?书中没有说,Let's try it!

    <RESTful Web APIs>书中有一段POST API示例: I then send the filled-out template as part of an HTTP POST ...

  7. 2020我终于成功搭建了Metasploitable3靶机

    0x00前言 在学习metasploit时我们往往需要一个靶场,下面为大家介绍一下如何在虚拟机中安装metasploitable 3靶场.Metasploitable3是Metasploitable2 ...

  8. C++中cstring.h和string.h的区别

    转载:https://blog.csdn.net/qian_chun_qiang/article/details/80648691 1.string与cstring有什么区别 <string&g ...

  9. 【题解】[JSOI2007]字符加密

    Link \(\text{Solution:}\) 后缀数组第一题祭-- 观察一下,这个是让求一个环形的原字符串的后缀,我们可以考虑一下断环为链. 对于\(aba\)我们扩展成\(abaaba,\)则 ...

  10. 如何部署MongoDB并开启远程访问Docker版

    Docker安装 安装方法 pull最新版本mongo docker pull mongo 运行 --name设置名称 -v挂载数据 -p端口映射 -d后台运行 mkdir ~/mongo #随便啦自 ...