2022-09-Guava的用法

一、概述

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

guava的优点:

高效设计良好的API,被Google的开发者设计,实现和使用
遵循高效的java语法实践
使代码更刻度,简洁,简单
节约时间,资源,提高生产力
Guava工程包含了若干被Google的 Java项目广泛依赖 的核心库,例如:

集合 [collections]
缓存 [caching]
原生类型支持 [primitives support]
并发库 [concurrency libraries]
通用注解 [common annotations]
字符串处理 [string processing]
I/O 等等。
二、基本使用

pom依赖

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

三、Guava的具体应用

1 String的相关操作

1.1 字符串拼接
字符串的拼接采用的是Guava中的Joiner类中的方法

@Test
    public void testJdkJoin() {
        List<String> strList = Lists.newArrayList("1", "2", null, "3", "4");
        //字符串拼接,无法跳过空字符串
        String str = String.join(",", strList);
        Assert.assertEquals("1,2,3,4", str);
    }

    @Test
    public void testGuavaJoin() {
        List<String> strList = Lists.newArrayList("1", "2", null, "3", "4");
        //字符串拼接,跳过空字符串
        String str = Joiner.on(",").skipNulls().join(strList);
        Assert.assertEquals("1,2,3,4", str);
    }

1.2字符串分割

@Test
    public void testJdkSplit(){
        String str = "1,2,   3  ,,4,";
        //trimResults():去除空格,omitEmptyStrings():删除空数组
        String[] strList = str.split(",");
        //  List<String> strList = Splitter.on(",").trimResults().omitEmptyStrings().splitToList(str);
        for (int i = 0; i < strList.length; i++) {
            String s = strList[i];
            System.out.println(s);
        }
        Assert.assertEquals(4,strList.length);
    }

    @Test
    public void testGuavaSplit(){
        String str = "1,2,   3  ,,4,";
        //trimResults():去除空格,omitEmptyStrings():删除空数组
        List<String> strList = Splitter.on(",").trimResults().omitEmptyStrings().splitToList(str);
        strList.forEach(s -> System.out.println(s));
        Assert.assertEquals(4,strList.size());
    }

2 不可变集合
2.1为什么使用不可变集合
不可变对象有很多优点,包括:

当对象被不可信的库调用时,不可变形式是安全的;
不可变对象被多个线程调用时,不存在竞态条件问题
不可变集合不需要考虑变化,因此可以节省时间和空间。所有不可变的集合都比它们的可变形式有更好的内存利用率(分析和测试细节);
不可变对象因为有固定不变,可以作为常量来安全使用。

所有Guava不可变集合的实现都不接受null值。我们对Google内部的代码库做过详细研究,发现只有5%的情况需要在集合中允许null元素,剩下的95%场景都是遇到null值就快速失败。如果你需要在不可变集合中使用null,请使用JDK中的Collections.unmodifiableXXX方法。更多细节建议请参考“使用和避免null”。
2.2、JDK中的Collections.unmodifiableXXX方法

// jdk
    @Test
    public void testJDKImmutable(){
        List<String> list=new ArrayList<String>();
        list.add("a");
        list.add("b");
        list.add("c");

        //通过list创建一个不可变的unmodifiableList集合
        List<String> unmodifiableList= Collections.unmodifiableList(list);
        System.out.println(unmodifiableList);

        //通过list添加元素
        list.add("ddd");
        System.out.println("往list添加一个元素:"+list);
        System.out.println("通过list添加元素之后的unmodifiableList:"+unmodifiableList);

        //通过unmodifiableList添加元素
        unmodifiableList.add("eee");
        System.out.println("往unmodifiableList添加一个元素:"+unmodifiableList);
    }

输出

[a, b, c]
往list添加一个元素:[a, b, c, ddd]
通过list添加元素之后的unmodifiableList:[a, b, c, ddd]

java.lang.UnsupportedOperationException
    at java.util.Collections$UnmodifiableCollection.add(Collections.java:1055)

通过运行结果我们可以看出:虽然unmodifiableList不可以直接添加元素,但是我的list是可以添加元素的,而list的改变也会使unmodifiableList改变。

所以说Collections.unmodifiableList实现的不是真正的不可变集合。
2.3 Guava 的immutable集合
Guava提供了对JDK里标准集合类里的immutable版本的简单方便的实现,以及Guava自己的一些专门集合类的immutable实现。当你不希望修改一个集合类,

或者想做一个常量集合类的时候,使用immutable集合类就是一个最佳的编程实践。

注意:每个Guava immutable集合类的实现都拒绝null值。我们做过对Google内部代码的全面的调查,并且发现只有5%的情况下集合类允许null值,而95%的情况下

都拒绝null值。万一你真的需要能接受null值的集合类,你可以考虑用Collections.unmodifiableXXX。

immutable集合可以有以下几种方式来创建:

1、用copyOf方法, 譬如, ImmutableSet.copyOf(set)

2、使用of方法,譬如,ImmutableSet.of(“a”, “b”, “c”)或者ImmutableMap.of(“a”, 1, “b”, 2)

3、使用Builder类

@Test
    public void testGuavaImmutable(){
        List<String> list=new ArrayList<String>();
        list.add("a");
        list.add("b");
        list.add("c");

        //ImmutableList.copyOf
        ImmutableList<String> imlist=ImmutableList.copyOf(list);
        System.out.println("imlist:"+imlist);

        //ImmutableList.of
        ImmutableList<String> imOflist=ImmutableList.of("peida","jerry","harry");
        System.out.println("imOflist:"+imOflist);

        ImmutableSortedSet<String> imSortList=ImmutableSortedSet.of("a", "b", "c", "a", "d", "b");
        System.out.println("imSortList:"+imSortList);

        list.add("baby");
        //关键看这里是否imlist也添加新元素了
        System.out.println("list添加新元素之后看imlist:"+imlist);

        ImmutableSet<Color> imColorSet =
                ImmutableSet.<Color>builder()
                        .add(new Color(0, 255, 255))
                        .add(new Color(0, 191, 255))
                        .build();

        System.out.println("imColorSet:"+imColorSet);
    }

智能的copyOf

copyOf方法比你想象的要智能,ImmutableXXX.copyOf会在合适的情况下避免拷贝元素的操作-先忽略具体的细节,但是它的实现一般都是很“智能”的。譬如:

@Test
    public void testCotyOf(){
        ImmutableSet<String> imSet=ImmutableSet.of("peida","jerry","harry","lisa");
        System.out.println("imSet:"+imSet);

        //set直接转list
        ImmutableList<String> imlist=ImmutableList.copyOf(imSet);
        System.out.println("imlist:"+imlist);

        //list直接转SortedSet
        ImmutableSortedSet<String> imSortSet=ImmutableSortedSet.copyOf(imSet);
        System.out.println("imSortSet:"+imSortSet);

        List<String> list=new ArrayList<String>();
        for(int i=0;i<=10;i++){
            list.add(i+"x");
        }
        System.out.println("list:"+list);

        //截取集合部分元素
        ImmutableList<String> imInfolist=ImmutableList.copyOf(list.subList(2, 8));
        System.out.println("imInfolist:"+imInfolist);
    }

3 集合
Guava引入了很多JDK没有的、但明显有用的新集合类型。这些新类型是为了和JDK集合框架共存,而没有往JDK集合抽象中硬塞其他概念。作为一般规则,Guava集合非常精准地遵循了JDK接口契约。
3.1、MultiSet[无序+可重复]-工具类Multisets
Guava提供了一个新集合类型 Multiset,它可以多次添加相等的元素。维基百科从数学角度这样定义Multiset:”集合[set]概念的延伸,它的元素可以重复出现…与集合[set]相同而与元组[tuple]相反的是,Multiset元素的顺序是无关紧要的:Multiset {a, a, b}和{a, b, a}是相等的”。——译者注:这里所说的集合[set]是数学上的概念,Multiset继承自JDK中的Collection接口,而不是Set接口,所以包含重复元素并没有违反原有的接口契约。

这个接口没有实现java.util.Set接口,Set接口规定里面是不能够放入重复的元素,如果放入重复元素会被覆盖掉的;然而Multiset接口却是可以放入重复元素的,Set接口中的元素是[1,2,3],Multiset中确可以[1✖️2,2✖️3,3✖️3]来表示多个相同的元素。

@Test
    public void testMultiSet(){
        Multiset<String> multiset= HashMultiset.create();
        multiset.add("aa");
        multiset.add("bb");
        multiset.add("cc",2);
        System.out.println(multiset);//[aa, bb, cc x 2]
        System.out.println(multiset.size()); //4
        System.out.println(multiset.count("cc"));//2
        multiset.setCount("bb",4);
        System.out.println(multiset);//[aa, bb x 4, cc x 2]
    }

3.2、SortedMultiset
SortedMultiset是Multiset 接口的变种,它支持高效地获取指定范围的子集。比方说,你可以用 latencies.subMultiset(0,BoundType.CLOSED, 100, BoundType.OPEN).size()来统计你的站点中延迟在100毫秒以内的访问,然后把这个值和latencies.size()相比,以获取这个延迟水平在总体访问中的比例。
3.3、MultiMap[key-value key可以重复 ]-工具类Multimaps
程序开发中使用Map<K, List>或Map<K, Set>,并且要忍受这个结构的笨拙。例如,Map<K, Set>通常用来表示非标定有向图。Guava的 Multimap可以很容易地把一个键映射到多个值。换句话说,Multimap是把键映射到任意多个值的一般方式。

很少会直接使用Multimap接口,更多时候你会用ListMultimap或SetMultimap接口,它们分别把键映射到List或Set。

Multimap提供了多种形式的实现。在大多数要使用Map<K, Collection>的地方,你都可以使用它们
除了两个不可变形式的实现,其他所有实现都支持null键和null值

*LinkedListMultimap.entries()保留了所有键和值的迭代顺序。详情见doc链接。

**LinkedHashMultimap保留了映射项的插入顺序,包括键插入的顺序,以及键映射的所有值的插入顺序。

请注意,并非所有的Multimap都和上面列出的一样,使用Map<K, Collection>来实现(特别是,一些Multimap实现用了自定义的hashTable,以最小化开销)

如果你想要更大的定制化,请用Multimaps.newMultimap(Map, Supplier)或list和 set版本,使用自定义的Collection、List或Set实现Multimap。

@Test
    public void testMultiMap() {
        Multimap<String, String> multimap = ArrayListMultimap.create();
        multimap.put("fruit", "bannana");
        multimap.put("fruit", "apple");//key可以重复
        multimap.put("fruit", "apple");//value可以重复,不会覆盖之前的
        multimap.put("fruit", "peach");
        multimap.put("fish", "crucian");//欧洲鲫鱼
        multimap.put("fish", "carp");//鲤鱼
        Collection<String> fruits = multimap.get("fruit");
        System.err.println(fruits);//[bannana, apple, apple, peach]
        
        //对比 HashMultimap
        Multimap<String,String> multimap2= HashMultimap.create();
        multimap2.put("fruit2", "bannana");
        multimap2.put("fruit2", "apple");
        multimap2.put("fruit2", "apple");

        System.err.println(multimap2.size());//2
        System.err.println(multimap2.get("fruit2"));//[apple, bannana]     注意: 这里只有一个apple
    }

3.4、BiMap[双向Map(Bidirectional Map) 键与值都不能重复]
传统上,实现键值对的双向映射需要维护两个单独的map,并保持它们间的同步。但这种方式很容易出错,而且对于值已经在map中的情况,会变得非常混乱。例如:

Map<String, Integer> nameToId = Maps.newHashMap();
Map<Integer, String> idToName = Maps.newHashMap();
nameToId.put("Bob", 42);
idToName.put(42, "Bob");
//如果"Bob"和42已经在map中了,会发生什么?
//如果我们忘了同步两个map,会有诡异的bug发生...

BiMap<K, V>是特殊的Map:

可以用 inverse()反转BiMap<K, V>的键值映射
  保证值是唯一的,因此 values()返回Set而不是普通的Collection
  在BiMap中,如果你想把键映射到已经存在的值,会抛出IllegalArgumentException异常。如果对特定值,你想要强制替换它的键,请使用 BiMap.forcePut(key, value)。

@Test
    public void testBiMap() {
        BiMap<String, Integer> userId = HashBiMap.create();
        userId.put("lhx",30);
        userId.put("zll",28);
        String userForId = userId.inverse().get(30);
        System.out.println(userForId);//lhx

        userId.put("jm",30);//报错
        String userForId2 = userId.inverse().get(30);
        System.out.println(userForId2);//lhx
    }

3.5、Table【双键的Map Map–> Table–>rowKey+columnKey+value //和sql中的联合主键有点像】-工具类Tables

行、列、值。当使用多个键做索引的时候,可能会用类似Map<FirstName, Map<LastName, Person>>的实现,这种方式很丑陋,使用上也不友好。

Guava为此提供了新集合类型Table,它有两个支持所有类型的键:”行”和”列”。

Table有如下几种实现:

HashBasedTable:本质上用HashMap<R, HashMap<C, V>>实现;
TreeBasedTable:本质上用TreeMap<R, TreeMap<C,V>>实现;
ImmutableTable:本质上用ImmutableMap<R, ImmutableMap<C, V>>实现;注:ImmutableTable对稀疏或密集的数据集都有优化。
ArrayTable:要求在构造时就指定行和列的大小,本质上由一个二维数组实现,以提升访问速度和密集Table的内存利用率。ArrayTable与其他Table的工作原理有点不同,请参见Javadoc了解详情。

@Test
    public void testTable() {
        Table<String, String, Integer> table = HashBasedTable.create();
        table.put("a", "b", 4);
        table.put("a", "c", 20);
        table.put("b", "c", 5);

        Map<String, Integer> a = table.row("a");// returns a Map mapping {b=4, c=20}
        System.out.println(a);

        Map<String, Integer> column = table.column("c");// returns a Map mapping {a=20, b=5}
        System.out.println(column);

        Integer integer = table.get("a", "c");
        System.out.println(integer); //20
    }

4 Guava 缓存
Guava Cache与ConcurrentMap很相似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素。在某些场景下,尽管LoadingCache 不回收元素,它也是很有用的,因为它会自动加载缓存。

通常来说,Guava Cache适用于:

你愿意消耗一些内存空间来提升速度。
你预料到某些键会被查询一次以上。
缓存中存放的数据总量不会超出内存容量。(Guava Cache是单个应用运行时的本地缓存。它不把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试redis这类工具)
如果你的场景符合上述的每一条,Guava Cache就适合你。

注:如果你不需要Cache中的特性,使用ConcurrentHashMap有更好的内存效率——但Cache的大多数特性都很难基于旧有的ConcurrentMap复制,甚至根本不可能做到。
4.1、Guava Cache有以下两种创建方式:
通过这两种方法创建的cache,和通常用map来缓存的做法比,不同在于,这两种方法都实现了一种逻辑——从缓存中取key X的值,如果该值已经缓存过了,则返回缓存中的值,如果没有缓存过,可以通过某个方法来获取这个值。但不同的在于cacheloader的定义比较宽泛,是针对整个cache定义的,可以认为是统一的根据key值load value的方法。而callable的方式较为灵活,允许你在get的时候指定。

方式一、创建 CacheLoader

LoadingCache是附带CacheLoader构建而成的缓存实现。创建自己的CacheLoader通常只需要简单地实现V load(K key) throws Exception方法。例如,你可以用下面的代码构建LoadingCache:
  CacheLoader: 当检索不存在的时候,会自动的加载信息的

class Person{
        private String name;

        public Person(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
    public com.google.common.cache.CacheLoader<String, Person> createCacheLoader() {
        return new com.google.common.cache.CacheLoader<String, Person>() {
            @Override
            public Person load(String key) throws Exception {
                System.out.println("加载创建key:" + key);
                return new Person(key+":ddd");
            }
        };
    }

    @Test
    public void testCreateCacheLoader() throws ExecutionException {
        LoadingCache<String, Person> cache = CacheBuilder.newBuilder()
                .maximumSize(1000)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build(createCacheLoader());
        cache.put("aa",new Person("aaa"));
        Person aa = cache.get("aa");
        System.out.println(aa);//Person{name='aaa'}


        Person bb = cache.get("bb");
        System.out.println(bb); //加载创建key:bb   Person{name='bb:ddd'}
    }

方式二、创建 Callable

@Test
    public void testCreateCallable() throws Exception {
        Cache<String, Person> cache = CacheBuilder.newBuilder()
                .maximumSize(1000)
                .build(); // look Ma, no CacheLoader

        try {
            cache.put("aa", new Person("aaaa"));
            // If the key wasn't in the "easy to compute" group, we need to
            // do things the hard way.
            Person aa = cache.get("aa", new Callable<Person>() {
                @Override
                public Person call() throws Exception {
                    return new Person("defalut");
//                    return doThingsTheHardWay(key);
                }
            });
            System.out.println(aa);//Person{name='aaaa'}
        } catch (Exception e) {
            throw new Exception(e.getCause());
        }

        Person bb = cache.get("bb", () -> new Person("defalut"));
        System.out.println(bb); //Person{name='defalut'}
    }

4.2、显示插入数据
使用cache.put(key, value)方法可以直接向缓存中插入值,这会直接覆盖掉给定键之前映射的值。使用Cache.asMap()视图提供的任何方法也能修改缓存。但请注意,asMap视图的任何方法都不能保证缓存项被原子地加载到缓存中

进一步说,asMap视图的原子运算在Guava Cache的原子加载范畴之外,所以相比于Cache.asMap().putIfAbsent(K,V),Cache.get(K, Callable) 应该总是优先使用。
  
4.3、缓存回收
Guava Cache提供了三种基本的缓存回收方式:基于容量回收、定时回收和基于引用回收。
1、基于容量的回收(size-based eviction)
  大小

如果要规定缓存项的数目不超过固定值,只需使用CacheBuilder.maximumSize(long)。缓存将尝试回收最近没有使用或总体上很少使用的缓存项。

警告:在缓存项的数目达到限定值之前,缓存就可能进行回收操作——通常来说,这种情况发生在缓存项的数目逼近限定值时。

权重

另外,不同的缓存项有不同的“权重”(weights)——例如,如果你的缓存值,占据完全不同的内存空间,你可以使用CacheBuilder.weigher(Weigher)指定一个权重函数,并且用CacheBuilder.maximumWeight(long)指定最大总重。在权重限定场景中,除了要注意回收也是在重量逼近限定值时就进行了,还要知道重量是在缓存创建时计算的,因此要考虑重量计算的复杂度。

@Test
    public void testWeight() throws Exception {
        LoadingCache<String, Person> cache = CacheBuilder.newBuilder()
                .maximumWeight(5)
                .weigher((Weigher<String, Person>) (s, person) -> {
                    //权重计算器
                    int weight = person.name.length();
                    System.out.println("key:"+s);
                    return weight;
                })
                .build(new CacheLoader<String, Person>() {
                    @Override
                    public Person load(String key) {
                        System.out.println("加载创建key:" + key);
                        return new Person(key + ":default");
                    }
                });

        cache.put("a",new Person("aaaaaaa1"));
        cache.put("b",new Person("bbbbbb1"));
        cache.put("c",new Person("cc1"));

        Person a = cache.get("a");
        System.out.println(a);
        Person b = cache.get("b");
        System.out.println(b);
        Person c = cache.get("c");
        System.out.println(c);

        //缓存只有 一个 c
        System.out.println(cache.asMap());
    }

2、定时回收(Timed Eviction)
acheBuilder提供两种定时回收的方法:

expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。【读一次多久后没有被访问过期】
expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。【写完多久后过期】
如下文所讨论,定时回收周期性地在写操作中执行,偶尔在读操作中执行。

@Test
    public void testEvictionByAccessTime() throws ExecutionException, InterruptedException {
        LoadingCache<String, Person> cache = CacheBuilder.newBuilder()
                .expireAfterAccess(2, TimeUnit.SECONDS)
                .build(createCacheLoader());
        cache.getUnchecked("wangji");
        TimeUnit.SECONDS.sleep(3);
        Person employee = cache.getIfPresent("wangji"); //不会重新加载创建cache
        System.out.println("被销毁:" + (employee == null ? "是的" : "否"));
        cache.getUnchecked("guava");

        TimeUnit.SECONDS.sleep(1);
        employee = cache.getIfPresent("guava"); //会重新加载创建cache
        System.out.println("被销毁:" + (employee == null ? "是的" : "否"));

        TimeUnit.SECONDS.sleep(2);
        employee = cache.getIfPresent("guava"); //不会重新加载创建cache
        System.out.println("被销毁:" + (employee == null ? "是的" : "否"));

        TimeUnit.SECONDS.sleep(2);
        employee = cache.getIfPresent("guava"); //不会重新加载创建cache
        System.out.println("被销毁:" + (employee == null ? "是的" : "否"));

        TimeUnit.SECONDS.sleep(2);
        employee = cache.getIfPresent("guava"); //不会重新加载创建cache
        System.out.println("被销毁:" + (employee == null ? "是的" : "否"));
    }

输出

加载创建key:wangji
被销毁:是的
加载创建key:guava
被销毁:否
被销毁:是的
被销毁:是的
被销毁:是的

3、基于引用的回收(Reference-based Eviction)【强(strong)、软(soft)、弱(weak)、虚(phantom】
通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache可以把缓存设置为允许垃圾回收:

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

清理什么时候发生

使用CacheBuilder构建的缓存不会”自动”执行清理和回收工作,也不会在某个缓存项过期后马上清理,也没有诸如此类的清理机制。相反,它会在写操作时顺带做少量的维护工作,或者偶尔在读操作时做——如果写操作实在太少的话。

这样做的原因在于:如果要自动地持续清理缓存,就必须有一个线程,这个线程会和用户操作竞争共享锁。此外,某些环境下线程创建可能受限制,这样CacheBuilder就不可用了。

相反,我们把选择权交到你手里。如果你的缓存是高吞吐的,那就无需担心缓存的维护和清理等工作。如果你的 缓存只会偶尔有写操作,而你又不想清理工作阻碍了读操作,那么可以创建自己的维护线程,以固定的时间间隔调用Cache.cleanUp()。

ScheduledExecutorService可以帮助你很好地实现这样的定时调度。

刷新

刷新和回收不太一样。正如LoadingCache.refresh(K)所声明,刷新表示为键加载新值,这个过程可以是异步的。在刷新操作进行时,缓存仍然可以向其他线程返回旧值,而不像回收操作,读缓存的线程必须等待新值加载完成。
5、移除监听器
通过CacheBuilder.removalListener(RemovalListener),你可以声明一个监听器,以便缓存项被移除时做一些额外操作。缓存项被移除时,RemovalListener会获取移除通知[RemovalNotification],其中包含移除原因[RemovalCause]、键和值。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值