Guava - 拯救垃圾代码,写出优雅高效,效率提高N倍

Google Guava

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

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

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

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.0-jre</version>
</dependency>

<!-- more -->git

数据校验

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

即便这么简单的操做,咱们是否是还常常出错呢?并且写起来的代码老是一行判断一行异常抛出,怎么看都以为那么优雅。还好,如今就来尝试第一次使用 Guava 吧。github

非空判断

String param = "未读代码";
String name = Preconditions.checkNotNull(param);
System.out.println(name); // 未读代码
String param2 = null;
String name2 = Preconditions.checkNotNull(param2); // NullPointerException
System.out.println(name2);

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

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

预期值判断

和非空判断相似,能够比较当前值和预期值,若是不相等能够自定义报错信息抛出。数组

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

是否越界

Preconditions 类还能够用来检查数组和集合的元素获取是否越界。缓存

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

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

不可变的集合

建立不可变集合是我我的最喜欢 Guava 的一个缘由,由于建立一个不能删除、不能修改、不能增长元素的集合实在是太实用了。这样的集合你彻底不用担忧发生什么问题,总的来讲有下面几个优势:多线程

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

建立方式

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

// 建立方式1:of
ImmutableSet<String> immutableSet = ImmutableSet.of("a", "b", "c");
immutableSet.forEach(System.out::println);
// a
// b
// c

// 建立方式2:builder
ImmutableSet<String> immutableSet2 = ImmutableSet.<String>builder()
    .add("hello")
    .add(new String("未读代码"))
    .build();
immutableSet2.forEach(System.out::println);
// hello
// 未读代码

// 建立方式3:从其余集合中拷贝建立
ArrayList<String> arrayList = new ArrayList();
arrayList.add("www.wdbyte.com");
arrayList.add("https");
ImmutableSet<String> immutableSet3 = ImmutableSet.copyOf(arrayList);
immutableSet3.forEach(System.out::println);
// www.wdbyte.com
// https

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

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

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

注意事项

  1. 使用 Guava 建立的不可变集合是拒绝 null 值的,由于在 Google 内部调查中,95% 的状况下都不须要放入 null 值。
  2. 使用 JDK 提供的不可变集合建立成功后,原集合添加元素会体如今不可变集合中,而 Guava 的不可变集合不会有这个问题。
List<String> arrayList = new ArrayList<>();
   arrayList.add("a");
   arrayList.add("b");
   List<String> jdkList = Collections.unmodifiableList(arrayList);
   ImmutableList<String> immutableList = ImmutableList.copyOf(arrayList);
   arrayList.add("ccc");
   jdkList.forEach(System.out::println);// result: a b ccc
   System.out.println("-------");
   immutableList.forEach(System.out::println);// result: a b
  1. 若是不可变集合的元素是引用对象,那么引用对象的属性是能够更改的。

其余不可变集合

不可变集合除了上面演示的 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 仍是增长了一些十分好用的方法,保证让你用上一次就爱不释手,

建立集合。

// 建立一个 ArrayList 集合
List<String> list1 = Lists.newArrayList();
// 建立一个 ArrayList 集合,同时塞入3个数据
List<String> list2 = Lists.newArrayList("a", "b", "c");
// 建立一个 ArrayList 集合,容量初始化为10
List<String> list3 = Lists.newArrayListWithCapacity(10);

LinkedList<String> linkedList1 = Lists.newLinkedList();
CopyOnWriteArrayList<String> cowArrayList = Lists.newCopyOnWriteArrayList();

HashMap<Object, Object> hashMap = Maps.newHashMap();
ConcurrentMap<Object, Object> concurrentMap = Maps.newConcurrentMap();
TreeMap<Comparable, Object> treeMap = Maps.newTreeMap();

HashSet<Object> hashSet = Sets.newHashSet();
HashSet<String> newHashSet = Sets.newHashSet("a", "a", "b", "c");

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

集合交集并集差集

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

Set<String> newHashSet1 = Sets.newHashSet("a", "a", "b", "c");
Set<String> newHashSet2 = Sets.newHashSet("b", "b", "c", "d");

// 交集
SetView<String> intersectionSet = Sets.intersection(newHashSet1, newHashSet2);
System.out.println(intersectionSet); // [b, c]

// 并集
SetView<String> unionSet = Sets.union(newHashSet1, newHashSet2);
System.out.println(unionSet); // [a, b, c, d]

// newHashSet1 中存在,newHashSet2 中不存在
SetView<String> setView = Sets.difference(newHashSet1, newHashSet2);
System.out.println(setView); // [a]

有数量的集合

这个真的太有用了,由于咱们常常会须要设计能够计数的集合,或者 value 是 ListMap 集合,若是说你不太明白,看下面这段代码,是否某天夜里你也这样写过。

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

    JDK 原生写法:

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

尽管已经尽可能优化代码,代码量仍是很多的,那么在 Guava 中有什么不同呢?在 Guava. 中主要是使用 HashMultiset 类,看下面。

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

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

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

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

    JDK 原生写法:

HashMap<String, Set<String>> animalMap = new HashMap<>();
   HashSet<String> dogSet = new HashSet<>();
   dogSet.add("旺财");
   dogSet.add("大黄");
   animalMap.put("狗", dogSet);
   HashSet<String> catSet = new HashSet<>();
   catSet.add("加菲");
   catSet.add("汤姆");
   animalMap.put("猫", catSet);
   System.out.println(animalMap.get("猫")); // [加菲, 汤姆]

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

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

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

字符串操做

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

字符拼接

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

// JDK 方式一
ArrayList<String> list = Lists.newArrayList("a", "b", "c", null);
String join = String.join(",", list);
System.out.println(join); // a,b,c,null
// JDK 方式二
String result = list.stream().collect(Collectors.joining(","));
System.out.println(result); // a,b,c,null
// JDK 方式三
StringJoiner stringJoiner = new StringJoiner(",");
list.forEach(stringJoiner::add);
System.out.println(stringJoiner.toString()); // a,b,c,null

能够看到 null 值也被拼接到了字符串里,这有时候不是咱们想要的,那么使用 Guava 有什么不同呢?

ArrayList<String> list = Lists.newArrayList("a", "b", "c", null);
String join = Joiner.on(",").skipNulls().join(list);
System.out.println(join); // a,b,c

String join1 = Joiner.on(",").useForNull("空值").join("旺财", "汤姆", "杰瑞", null);
System.out.println(join1); // 旺财,汤姆,杰瑞,空值

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

字符串分割

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

String str = ",a,,b,";
String[] splitArr = str.split(",");
Arrays.stream(splitArr).forEach(System.out::println);
System.out.println("------");
/**
 *
 * a
 * 
 * b
 * ------
 */

你也能够本身测试下,最后一个元素不是空,直接消失了。

若是使用 Guava 是怎样的操做方式呢?Guava 提供了 Splitter 类,而且有一系列的操做方式能够直观的控制分割逻辑。

String str = ",a ,,b ,";
Iterable<String> split = Splitter.on(",")
    .omitEmptyStrings() // 忽略空值
    .trimResults() // 过滤结果中的空白
    .split(str);
split.forEach(System.out::println);
/**
 * a
 * b
 */

缓存

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

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

@Test
public void testCache() throws ExecutionException, InterruptedException {

    CacheLoader cacheLoader = new CacheLoader<String, Animal>() {
        // 若是找不到元素,会调用这里
        @Override
        public Animal load(String s) {
            return null;
        }
    };
    LoadingCache<String, Animal> loadingCache = CacheBuilder.newBuilder()
        .maximumSize(1000) // 容量
        .expireAfterWrite(3, TimeUnit.SECONDS) // 过时时间
        .removalListener(new MyRemovalListener()) // 失效监听器
        .build(cacheLoader); //
    loadingCache.put("狗", new Animal("旺财", 1));
    loadingCache.put("猫", new Animal("汤姆", 3));
    loadingCache.put("狼", new Animal("灰太狼", 4));

    loadingCache.invalidate("猫"); // 手动失效

    Animal animal = loadingCache.get("狼");
    System.out.println(animal);
    Thread.sleep(4 * 1000);
    // 狼已经自动过去,获取为 null 值报错
    System.out.println(loadingCache.get("狼"));
    /**
     * key=猫,value=Animal{name='汤姆', age=3},reason=EXPLICIT
     * Animal{name='灰太狼', age=4}
     * key=狗,value=Animal{name='旺财', age=1},reason=EXPIRED
     * key=狼,value=Animal{name='灰太狼', age=4},reason=EXPIRED
     *
     * com.google.common.cache.CacheLoader$InvalidCacheLoadException: CacheLoader returned null for key 狼.
     */
}

/**
 * 缓存移除监听器
 */
class MyRemovalListener implements RemovalListener<String, Animal> {

    @Override
    public void onRemoval(RemovalNotification<String, Animal> notification) {
        String reason = String.format("key=%s,value=%s,reason=%s", notification.getKey(), notification.getValue(), notification.getCause());
        System.out.println(reason);
    }
}

class Animal {
    private String name;
    private Integer age;

    @Override
    public String toString() {
        return "Animal{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
    }

    public Animal(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}

这个例子中主要分为 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 和完善,但愿咱们一块儿变得优秀。

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

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

公众号

相关文章
相关标签/搜索