U2647's blog 一个热爱学习的 Java 程序员,喜欢 Vue,喜欢深度学习 Dubbo Flutter SpringBoot Debug Notes Java LeetCode Python Redis Android DesignPattern mdi-home-outline 首页 mdi-cloud-outline 标签云 mdi-timeline-text-outline 时间轴 mdi-draw-pen 文章总数 62
Spring Boot 学习笔记(十四) 整合 Guava Spring Boot 学习笔记(十四) 整合 Guava Spring Boot Guava mdi-cursor-default-click-outline 点击量 62

0. Guava

Guava 工程包含了若干被Google的 Java项目广泛依赖的核心库。或者说是 Google 工程师的瑞士军刀。Guava 里包含了集合 [collections] 、缓存 [caching] 、原生类型支持 [primitives support] 、并发库 [concurrency libraries] 、通用注解 [common annotations] 、字符串处理 [string processing] 、I/O 等等。 所有这些工具每天都在被Google的工程师应用在产品服务中。

想要使用 Guava 首先需要添加依赖。

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>28.1-jre</version>
</dependency>

1. Optional

Guava 用 Optional表示可能为 null 的 T 类型引用。一个 Optional 实例可能包含非 null 的引用(我们称之为引用存在),也可能什么也不包括(称之为引用缺失)。它从不说包含的是 null 值,而是用存在或缺失来表示。但 Optional 从不会包含 null 值引用。

1.1 简单使用

当某个方法有可能返回 null 值时候,我们可以返回一个 Optional 的泛型对象。代码如下:

    /**
     * 使用 empty 代替 null
     *
     * @param num
     * @return
     */
    private Optional<Integer> getNum(int num) {
        if (num > 0) {
            return Optional.of(num);
        } else {
            //使用 empty 代替 null
            return Optional.empty();
        }
    }

    public void testGetNum() {
        Optional<Integer> num = this.getNum(5);
        if (num.isPresent()) {
            System.out.println(num.get());
        }
        //也可以使用 ifPresent 方法
        num.ifPresent(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });
    } 

使用 Optional.of() 来创建一个可用对象,使用 Optional.empty(); 来创建一个不可引用的对象。用来替代之前直接返回 null 的情况。

使用 Optional 时,可以通过Optional.isPresent()方法,或者是Optional.ifPresent()方法来使用。

1.2 使用 filter 方法

可以使用 filter 方法 对返回结果做二次判断。

  1. 使用Predicates.in() 方法,判断返回结果是否在集合内。
    public static void testIn(){
        List<AccountInfo> data = new ArrayList<>();
        
        AccountInfo accountInfo = new AccountInfo();
        accountInfo.setName("aaa");
        accountInfo.setBalance(123);
        data.add(accountInfo);

        AccountInfo accountInfo2 = new AccountInfo();
        accountInfo2.setName("aaa");
        accountInfo2.setBalance(123);
        
        System.out.println("equals:"+accountInfo.equals(accountInfo2));

        Optional<AccountInfo> optional = Optional.of(accountInfo2);
        //通过 调用对象的 equals 方法来判断是否存在
        optional.filter(Predicates.in(data)).ifPresent(new Consumer<AccountInfo>() {
            @Override
            public void accept(AccountInfo accountInfo) {
                System.out.println("1:"+accountInfo.toString());
            }
        });
    }

in 方法调用的是对象的 equals 方法,所以如果对象的 equals 方法返回 true,那么就会访问到该对象,否则就访问不到。

Predicates 类还有一些其他方法,用于过滤 Optional 对象。可以参考 Predicates 的API。

2. Preconditions

Guava 在 Preconditions 类中提供了若干前置条件判断的实用方法。这些方法让调用的前置条件判断更简单。

    /**
     * 检查参数是否为 true
     * @param attr
     */
    private static void testAttrs(boolean attr){
        Preconditions.checkArgument(attr,"参数错误");
    }

    /**
     * 检查参数是否为 null
     * @param accountInfo
     */
    private static void testAttr2(AccountInfo accountInfo) {
        Preconditions.checkNotNull(accountInfo, "参数不能为 null");
    }

    /**
     * 检查索引是否越界
     * @param index
     * @param size
     */
    private static void testAttr3(int index, int size) {
        Preconditions.checkElementIndex(index, size);
    }

    /**
     * 检查区间是否越界
     * @param start
     * @param end
     * @param size
     */
    private static void testAttr4(int start,int end, int size) {
        Preconditions.checkPositionIndexes(start,end, size);
    }
    public static void main(String[] args) {
        //testAttrs(Boolean.FALSE);
        //testAttr2(null);
        //testAttr3(3, 2);
        testAttr4(2, 4, 3);
    }

Guava 建议直接静态导入,就不用 Preconditions.checkArgument(), 来调用了,直接使用 checkArgument(),比较简洁。

3. Ordering

排序器[Ordering]是 Guava 流畅风格比较器[Comparator]的实现,它可以用来为构建复杂的比较器,以完成集
合排序的功能。

Guava 提供了 3 中常见的比较器。

  • Ordering.natural()

默认比较器,针对可比较对象。

  • Ordering.usingToString()

使用字符串的字典顺序排序。

  • Ordering.arbitrary()

插入顺序的倒序。

还可以根据 Comparator 生成 Ordering.from(Comparator)

或者自定义比较器:

    private static void lengthOrdering(List<String> list) {
        Ordering<String> lengthOrdering = new Ordering<String>() {
            @Override
            public int compare(@Nullable String left, @Nullable String right) {
                return left.length() - right.length();
            }
        };
        list.sort(lengthOrdering);
        //倒序
        list.sort(lengthOrdering.reverse());

    }

如果要根据对象的某个具体的属性排序,可以使用 onResultOf 方法。

   private static void lengthOrdering2(List<AccountInfo> list) {
        Ordering<AccountInfo> lengthOrdering = Ordering.natural().nullsFirst().onResultOf(new Function<AccountInfo, Integer>() {
            @Nullable
            @Override
            public Integer apply(@Nullable AccountInfo accountInfo) {
                return accountInfo.getBalance();
            }
        });
        list.sort(lengthOrdering);
        //倒序
        list.sort(lengthOrdering.reverse());
    }

注意:这里跟平常的链式调用的不同是,执行顺序是从后往前执行!!!比如上面的例子。

Ordering.natural().nullsFirst().onResultOf()

首先执行 onResultOf 获取所有对象的 balance 值,然后将 null 值排在最前,然后再按照数字顺序排序。

Ordering 还提供了一些集合遍历、迭代的方法。比如:

  • greatestOf(Iterableiterable, int k)

获取最大的 k 个元素

  • min(Iterable)

获取最小的一个元素

  • isOrdered(Iterable)

判断对象是否按照给定的排序器排序

4. Collections

4.1 不可变集合

创建对象的不可变拷贝是一项很好的防御性编程技巧。Guava 为所有 JDK 标准集合类型和 Guava 新集合类型都提供了简单易用的不可变版本。

使用方式:

    public static void testImmutable(){
        ImmutableSet<String> immutableSet = ImmutableSet.<String>builder().add("c")
                .add("b").build();

        for (String s : immutableSet) {
            System.out.print(s + ",");
        }
    }

Guava 还提供了有序的不可变集合。

    private static void testSort(){
        ImmutableSortedSet<String> immutableSortedSet = ImmutableSortedSet.of("c", "a", "b");
        for (String s:immutableSortedSet) {
            System.out.print(s + ",");
        }
    }

4.2 新集合类型

  • Multiset

Guava 提供了一个新集合类型 Multiset ,它可以多次添加相等的元素。你可以将 Multiset 类比成 Map<E, Integer>,键为元素,值为计数的一个 Map 集合。添加相同元素会增加计数,删除元素会减少计数。

    private static void testMultiset(){
        Multiset<String> multiset = HashMultiset.create();
        multiset.add("a");
        multiset.add("a");
        multiset.add("b");
        multiset.add("c");
        multiset.setCount("b", 3);

        System.out.println(multiset.contains("a"));
        System.out.println(multiset.count("a"));
        System.out.println(multiset.remove("b"));
        System.out.println(multiset.count("b"));
        System.out.println(multiset.size());
    }

当然,Guava 还提供了 TreeMultiset、LinkedHashMultiset、ConcurrentHashMultiset、ImmutableMultiset
的实现,需要注意的是,这些类都提供了 create 方法,所以尽量不要使用 new 来创建对象。更贴心的是,Guava 还提供了一个 SortedMultiset ,默认排序。

    private static void testSortMultiset(){
        SortedMultiset<Integer> multiset = TreeMultiset.create();
        multiset.addAll(Lists.newArrayList(3, 2, 5, 6, 9, 2, 5, 6, 8));
        for (int num:multiset) {
            System.out.print(num + ",");
        }
    }
  • Multimap

Guava 的 Multimap 可以很容易地把一个键映射到多个值。换句话说,Multimap 是把键映射到任意多个值的一般方式。你可以将 Multimap 类比成 Map<K, List> 或 Map<K, Set>。

    private static void testMultimap(){
        Multimap<String, Integer> multimap = ArrayListMultimap.create();
        multimap.put("a", 1);
        multimap.put("a", 2);
        multimap.put("a", 3);
        multimap.put("a", 4);

        System.out.println(multimap.containsKey("a"));
        System.out.println(multimap.containsEntry("a", 1));
        System.out.println(multimap.containsEntry("a", 0));
        System.out.println(multimap.remove("a", 3));

        System.out.println(Arrays.deepToString(multimap.get("a").toArray()));

    }

Multimap 还提供了若干个视图

  • multimap.asMap()

返回一个 Map<K,Collection>形式的视图。

  • multimap.entries()

返回一个 Collection<Map.Entry<K, V>> 包含所有的键值对。

  • multimap.keySet()

返回一个 Set 集合,包含所有不重复的键

  • multimap.keys()

返回一个 Multiset 包含所有的 key。

  • multimap.values()

返回一个 Collection 包含所有的 value

Multimap 的具体实现类包括 ArrayListMultimap、HashMultimap、TreeMultimap、ImmutableListMultimap 等。

  • BiMap

BiMap 是一个特殊的 Map ,可以用 inverse()反转 BiMap<K, V>的键值映射。所以,在插入值时需要注意,保证值时唯一的。强制将某个值插入到 BiMap 中,会更新该值对应的 key。

    private static void testBiMap(){
        BiMap<String, Integer> biMap = HashBiMap.create();
        biMap.put("a", 1);
        biMap.put("b", 2);
        System.out.println(biMap.inverse().get(2));
        biMap.forcePut("c", 2);
        System.out.println(biMap.inverse().get(2));

    }

BiMap 的常见实现类 HashBiMap、ImmutableBiMap、EnumBiMap、EnumHashBiMap。

  • other
    还有一些不常用到的集合类,比如
  1. Table: 集合,可以根据行和列来存储和检索具体的值。
  2. ClassToInstanceMap: 是一个类型 Map,它的键是类型,而值是符合键所指类型的对象。
  3. RangeSet: 描述了一组不相连的区间。当把一个区间添加到可变的RangeSet时,所有相连的区间会被合并
  4. RangeMap: 将一个区间映射到一个对应的 value 上

5. collection utilities

Guava 提供了大量的集合工具。包括 Lists、Maps、Sets 等等。

5.1 静态工厂方法。

Guava 提供了能够推断范型的静态工厂方法。

     private static void testUtil1(){
        List<AccountInfo> accountInfoList = Lists.newArrayList();
        List<AccountInfo> accountInfoList2 = Lists.newArrayListWithCapacity(3);
        List<AccountInfo> accountInfoList3 = Lists.newArrayListWithExpectedSize(3);
        List<AccountInfo> accountInfoList4 = Lists.newArrayList(new AccountInfo());
    }

5.2 Iterables

Iterables 提供了一些集合操作的方法。

    private static void testUtil1(){
        List<AccountInfo> accountInfoList = Lists.newArrayList();
        List<AccountInfo> accountInfoList2 = Lists.newArrayListWithCapacity(3);
        List<AccountInfo> accountInfoList3 = Lists.newArrayListWithExpectedSize(3);
        List<AccountInfo> accountInfoList4 = Lists.newArrayList(new AccountInfo());

        Iterable<Integer> concatenated = Iterables.concat(Ints.asList(1, 2, 3),
                Ints.asList(3, 4, 5));

        System.out.println(Iterables.frequency(concatenated, 3));
        System.out.println(Iterables.getFirst(concatenated, 0));
        
    }
  • concat(Iterable)

串联多个 iterables 的懒视图

  • frequency(Iterable, Object)

返回对象在 iterable 中出现的次数

  • partition(Iterable, int)

把 iterable 按指定大小分割,得到的子集都不能进行修改操作

  • getFirst(Iterable, T default)

返回 iterable 的第一个元素,若 iterable 为空则返回默认值

  • getLast(Iterable)

返回 iterable 的最后一个元素,若 iterable 为空则抛出NoSuchElementException

  • elementsEqual(Iterable, Iterable)

如果两个 iterable 中的所有元素相等且顺序一致,返回 true

  • unmodifiableIterable(Iterable)

返回 iterable 的不可变视图

  • limit(Iterable, int)

限制 iterable 的元素个数限制给定值

  • getOnlyElement(Iterable)

获取 iterable 中唯一的元素,如果 iterable 为空或有多个元素,则快速失败

5.2 Sets

Guava 提供了很多标准的集合运算的方法。比如集合间的交集、并集、补集等

  • Sets.union()

返回两个集合的并集,并去除重复元素

  • Sets.intersection()

返回两个集合的交集

  • Sets.difference(set1,set2)

返回 set1 中,不存在在 set2 中的元素,或者说是 set2 在 set1 中的绝对补集

    private static void testSets() {
        Set<Integer> set = Sets.union(Sets.newHashSet(1, 2, 3), Sets.newHashSet(3, 4, 5));
        System.out.println(Arrays.deepToString(set.toArray()));

        Set<Integer> set2 = Sets.intersection(Sets.newHashSet(1, 2, 3), Sets.newHashSet(3, 4, 5));
        System.out.println(Arrays.deepToString(set2.toArray()));

        Set<Integer> set3 = Sets.difference(Sets.newHashSet(1, 2, 3), Sets.newHashSet(1,2));
        System.out.println(Arrays.deepToString(set3.toArray()));
    }

5.3 Maps

Maps 里有两个比较有意思的方法。

  • Maps.filterKeys() / Maps.filterEntries

根据给定的规则过滤键值对

  • Maps.difference

用来比较两个 Map 以获取所有不同点。

  • Maps.uniqueIndex

这个方法可以将一个 Iterable 对象 转成 Map 集合。

PS: 有点没看懂这个方法,有了解的朋友可以留言

    private static void testMaps() {
        Map<String, Integer> hashMap = Maps.newHashMap();
        hashMap.put("a", 1);
        hashMap.put("bb", 2);
        hashMap.put("ccc", 3);

        Map<String, Integer> hashMap1 = Maps.filterKeys(hashMap, new Predicate<String>() {
            @Override
            public boolean apply(@Nullable String input) {
                return input.length() > 2;
            }
        });

        System.out.println(Arrays.deepToString(hashMap1.entrySet().toArray()));

        Map<String, Integer> hashMap2 = Maps.filterEntries(hashMap, new Predicate<Map.Entry<String, Integer>>() {
            @Override
            public boolean apply(Map.@Nullable Entry<String, Integer> input) {
                return input.getValue() > 2;
            }
        });
        System.out.println(Arrays.deepToString(hashMap2.entrySet().toArray()));


        Map<Integer, String> hashMap3 = Maps.uniqueIndex(Lists.newArrayList("sss", "ss", "ssss"),
                new Function<String, Integer>() {
                    @Nullable
                    @Override
                    public Integer apply(@Nullable String input) {
                        return input.length();
                    }
                });
        System.out.println(Arrays.deepToString(hashMap3.entrySet().toArray()));

        Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);
        Map<String, Integer> right = ImmutableMap.of("a", 1, "b", 2, "c", 3);
        MapDifference<String, Integer> diff = Maps.difference(left, right);

        diff.entriesInCommon(); // {"b" => 2}
        diff.entriesInCommon(); // {"b" => 2}
        diff.entriesOnlyOnLeft(); // {"a" => 1}
        diff.entriesOnlyOnRight(); // {"d" => 5}MapDifference
    }

5.3 Forwarding

针对所有类型的集合接口,Guava 都提供了 Forwarding 抽象类以简化装饰者模式的使用

    private static void testForwarding(){
        ForwardingList<String> forwardingList = new ForwardingList<String>() {
            final List<String> delegate = new ArrayList<>(); // backing list
            @Override
            protected List<String> delegate() {
                return delegate;
            }

            @Override
            public void add(int index, String element) {
                System.out.println("add:" + element);
                super.add(index, element);
            }

            @Override
            public String get(int index) {
                System.out.println("get:" + index);
                return super.get(index);
            }
        };

        forwardingList.add(0,"aaa");
        System.out.println(forwardingList.get(0));
    }

类似于我们对 List 做了一层装饰(或者说说封装),在执行 List 的相关方法时,可以做一些其他的处理。

当然也可以通过一个类,来实现 Forwarding 的相关接口,比如:

    class AddLoggingList<E> extends ForwardingList<E> {
        final List<E> delegate; // 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); // 用add(int, E)实现
        }
        @Override public boolean addAll(Collection<? extends E> c) {
            return standardAddAll(c); // 用add实现
        }
    }

上面的 AddLoggingList 在使用时与普通的 ArrayList 并没有什么区别,唯一的区别是,使用 AddLoggingList 时,添加元素会打印出相应的 log。

6. Caches

通常来说,Guava Cache 适用于:

  • 你愿意消耗一些内存空间来提升速度。
  • 你预料到某些键会被查询一次以上。
  • 缓存中存放的数据总量不会超出内存容量。

如果你的场景符合上述的每一条,Guava Cache 就适合你。

6.1 LoadingCache

    private static LoadingCache<String, AccountInfo> loadingCache = CacheBuilder.newBuilder()
            //最大缓存数量
            .maximumSize(10).build(new CacheLoader<String, AccountInfo>() {
                @Override
                public AccountInfo load(String key) throws Exception {
                    System.out.println("load 方法执行:" + key);
                    AccountInfo accountInfo = new AccountInfo();
                    accountInfo.setName(key);
                    return accountInfo;
                }
            });
    private static void testCaches() throws ExecutionException {
        //如果缓存中没有该值,就会添加新值
        loadingCache.get("aaa").setBalance(123);
        System.out.println(loadingCache.get("aaa"));
    }

可以通过 Builder 来创建一个缓存对象(或者叫本地缓存池)。可以设定缓存对象的一些基本参数,比如最大数量等。

load 方法用来加载需要缓存的对象。这些对象可以是手动创建的,也可以是从DB、Redis等其他外部存储加载进来的。

可以直接使用 get() 方法获取 LoadingCache 中的对象。注意:这里如果在缓存中没有找到对应的值,通过 load 方法创建一个新的值添加到缓存中,并返回该值。

如果我们不希望 使用默认的 load 方法创建对象。也可以在使用 get 方法时传入一个 Call 对象,用来创建需要缓存的值。

    private static void testCaches() throws ExecutionException {
        //如果缓存中没有该值,就会添加新值
        loadingCache.get("aaa").setBalance(123);
        System.out.println(loadingCache.get("aaa"));
        //如果没有值,就会执行 call 方法,创建对应的值,并返回
        AccountInfo accountInfo = loadingCache.get("bbb", new Callable<AccountInfo>() {
            @Override
            public AccountInfo call() throws Exception {
                AccountInfo accountInfo = new AccountInfo();
                accountInfo.setName("bbb");
                accountInfo.setPwd("call-add");
                return accountInfo;
            }
        });
        System.out.println(accountInfo.toString());
    }

如果你执行了上面的代码,就会发现,获取 “aaa” 时,调用了 load 方法,而获取 “bbb” 时,则没有执行 load 方法。

也可以直接向缓存中插入值。

    loadingCache.put("ccc",accountInfo);
    System.out.println(loadingCache.get("ccc"));

不过,尽量优先使用 Cache.get(K, Callable) 方法

6.2 Eviction

Guava Cache 提供了三种基本的缓存回收方式:基于容量回收、定时回收和基于引用回收。

  • 基于容量回收

我们可以指定缓存的容量,通过 maximumSize() 方法,当超过容量时,缓存将尝试回收最近没有使用或总体上很少使用的缓存项

    private static LoadingCache<String, AccountInfo> loadingCache = CacheBuilder.newBuilder()
            //最大缓存数量
            .maximumSize(10).build(new CacheLoader<String, AccountInfo>() {
                @Override
                public AccountInfo load(String key) throws Exception {
                    System.out.println("load 方法执行:" + key);
                    AccountInfo accountInfo = new AccountInfo();
                    accountInfo.setName(key);
                    return accountInfo;
                }
            });

另外,Guava 缓存还提供了一种根据“权重(weights)” 来删除缓存的方式。你可以使用 CacheBuilder.weigher(Weigher)指定一个权重函数,并且用 CacheBuilder.maximumWeight(long)指定最大总重。

注意:maximumWeight() 方法与 maximumSize() 方法不能同时使用

    private static void testEviction() throws ExecutionException {
        LoadingCache<String, AccountInfo> cache = CacheBuilder.newBuilder()
                //最大缓存数量
                //.maximumSize(10)
                //最大权重
                .maximumWeight(10L)
                .weigher(new Weigher<String, AccountInfo>() {
                    @Override
                    public int weigh(String key, AccountInfo value) {
                        return value.getBalance();
                    }
                })
                .build(new CacheLoader<String, AccountInfo>() {
                    @Override
                    public AccountInfo load(String key) throws Exception {
                        AccountInfo accountInfo = new AccountInfo();
                        accountInfo.setName(key);
                        return accountInfo;
                    }
                });
        for (int i = 0; i < 12; i++) {
            AccountInfo accountInfo = new AccountInfo();
            accountInfo.setName("aaa" + i);
            accountInfo.setBalance(i);
            cache.put(accountInfo.getName(), accountInfo);
        }
        System.out.println(cache.asMap().size());
        System.out.println(Arrays.deepToString(cache.asMap().entrySet().toArray()));
    }
  • 定时回收

CacheBuilder 提供两种定时回收的方法:

  1. expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。
  2. expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。
  • 基于引用的回收

通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache 可以把缓存设置为允许垃圾回收:

  1. CacheBuilder.weakKeys():使用弱引用存储键。当键没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(==),使用弱引用键的缓存用==而不是 equals 比较键。
  2. CacheBuilder.weakValues():使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(==),使用弱引用值的缓存用==而不是 equals 比较值。
  3. CacheBuilder.softValues():使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。考虑到使用软引用的性能影响,我们通常建议使用更有性能预测性的缓存大小限定(见上文,基于容量回收)。使用软引用值的缓存同样用==而不是 equals 比较值。
  • 显式清除

任何时候,你都可以显式地清除缓存项,而不是等到它被回收:

  1. 个别清除:Cache.invalidate(key)
  2. 批量清除:Cache.invalidateAll(keys)
  3. 清除所有缓存项:Cache.invalidateAll()

注意:这里的清除缓存并不一定会删除元素,准确的说法是将缓存置为无效。如果你调用 Cache.asMap 方法,你会发现,里面的元素并没有少。但是当调用 invalidate() 方法之后,再次调用 get() 方法,会重新执行 load 方法,说明原来的数据已经是无效的了

7. Strings

7.1 Joiner

用分隔符把字符串序列连接起来也可能会遇上不必要的麻烦。如果字符串序列中含有 null,那连接操作会更难。Fluent 风格的 Joiner 让连接字符串更简单。

    private static void testJoiner(){
        //使用 '-' 链接,遇到 null 值用 '#' 代替
        Joiner joiner = Joiner.on("-").useForNull("#");
        System.out.println(joiner.join("aaa","bbb",null,"","ccc"));
        //忽略空值
        joiner = Joiner.on("-").skipNulls();
        //将拼接好的 String append 到 StringBuilder 之后
        StringBuilder sb = Joiner.on("-").appendTo(new StringBuilder("sss"), "aaa","bbb");
        System.out.println(sb.toString());
    }

7.2 Splitter

非常友好的 String 拆分器。

    private static void testSplitter(){
        //使用 ; 分隔,并且忽略结果集中的空字符串,移除结果中的开头和结尾的空白字符
        Splitter splitter = Splitter.on(';').omitEmptyStrings().trimResults();

        Iterable<String> arr = splitter.split("a;aa ;  a ;;;");
        for (String str : arr) {
            System.out.print(str+",");
        }
        System.out.println();
        //按照固定长度拆分
        arr = Splitter.fixedLength(3).split("aaabbbcccdd");
        for (String str : arr) {
            System.out.print(str+",");
        }
        System.out.println();
    }

7.3 CharMatcher

CharMatcher 实例代表着某一类字符,如数字或空白字符。使用 CharMatcher 的好处更在于它提供了一系列方法,让你对字符作特定类型的操作:修剪[trim]、折叠[collapse]、移除[remove]、保留[retain]等等。

    private static void testCharMatcher(){
        //去掉控制字符(回车、换行、tab等)
        String noControl = CharMatcher.javaIsoControl().removeFrom("aa\tbb\ncc\\d\\.");
        System.out.println(noControl);

        String theDigits = CharMatcher.inRange('0', '9').retainFrom("1a2b3c4d");
        System.out.println(theDigits);

        //去除两端的空格,并把中间的连续空格替换成 '-'
        String spaced = CharMatcher.whitespace().trimAndCollapseFrom("aaaa   bbb cc  ", '-');
        System.out.println(spaced);

        //用*号替换所有数字
        String noDigits = CharMatcher.inRange('0', '9').replaceFrom("1a2b3c4d", "*");
        System.out.println(noDigits);

        String lowerAndDigit = CharMatcher.inRange('0', '9').or(CharMatcher.javaLowerCase()).retainFrom("1Aa2Bb3Cc");
        System.out.println(lowerAndDigit);

    }

8. 参考

版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
我的GitHub 我的LeetCode 我的掘金
Powered by Hexo Powered by three-cards
Copyright © 2017 - {{ new Date().getFullYear() }} 某ICP备xxxxxxxx号