Map 有变动时触发特定行为实现

一、背景

在平时开发过程中,通常我们会选择使用 Guava 的 Cache 类用作本地缓存。

但有些时候,我们不需要过期控制,不想引入 Guava 包,我们也会选择简单粗暴的使用 Map 作缓存。

但是,在某些业务场景下,需要在 Map 的属性发生变更时,做一些特殊处理

比如 Map 发生不变化时,要基于 Map 的值生成一份不可变 List。

二、方法

尝试使用 GuavaCache 类,发现 CacheBuilder 构造 Cache 时,只提供了 RemovalListener 用于移除或替换元素的监听,并没有提供新增元素、清空map 等事件的监听。

因此,借鉴 SynchronizedList 的思想,即 SynchronizedList 作为包装器内部持有 list 的引用,这种思路,我们可以对 Map 进行改造。

static class SynchronizedList<E>
        extends SynchronizedCollection<E>
        implements List<E> {
        private static final long serialVersionUID = -7754090372962971524L;

        final List<E> list;

        SynchronizedList(List<E> list) {
            super(list);
            this.list = list;
        }
        SynchronizedList(List<E> list, Object mutex) {
            super(list, mutex);
            this.list = list;
        }

        public boolean equals(Object o) {
            if (this == o)
                return true;
            synchronized (mutex) {return list.equals(o);}
        }
        public int hashCode() {
            synchronized (mutex) {return list.hashCode();}
        }

        public E get(int index) {
            synchronized (mutex) {return list.get(index);}
        }
        public E set(int index, E element) {
            synchronized (mutex) {return list.set(index, element);}
        }
        public void add(int index, E element) {
            synchronized (mutex) {list.add(index, element);}
        }
        public E remove(int index) {
            synchronized (mutex) {return list.remove(index);}
        }

        public int indexOf(Object o) {
            synchronized (mutex) {return list.indexOf(o);}
        }
// 省略其他
      }

参考代码如下:

构造一个 ModifyHookMap 类,可以通过 Builder 传入底层使用的 Map 和需要感知的回调函数。

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Pair<K, V> {
    private K key;

    private V value;
}
import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Triple<F, S, T> {
    private F first;

    private S second;

    private T third;
}
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

public class ModifyHookMap<K, V> implements Map<K, V> {
    private final Map<K, V> map;

    private final Consumer<ModifyHookMap<K, V>> onModified;

    private final Consumer<Triple<K, V, V>> onPut;

    private final Consumer<Pair<K, V>> onRemove;

    private final Consumer<Map<? extends K, ? extends V>> onPutAll;

    private final Consumer<Void> onClear;

    private ModifyHookMap(Builder<K, V> builder) {
        map = builder.map;
        Consumer<Map<K, V>> onInit = builder.onInit;
        onModified = builder.onModified;
        onPut = builder.onPut;
        onRemove = builder.onRemove;
        onPutAll = builder.onPutAll;
        onClear = builder.onClear;

        if (onInit != null) {
            onInit.accept(map);
        }

        if (onModified != null) {
            onModified.accept(this);
        }
    }


    @Override
    public int size() {
        return map.size();
    }

    @Override
    public boolean isEmpty() {
        return map.isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
        return map.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        return map.containsValue(value);
    }

    @Override
    public V get(Object key) {
        return map.get(key);
    }

    @Override
    public V put(K key, V value) {
        V old = map.put(key, value);
        if (onPut != null) {
            onPut.accept(new Triple<>(key, value, old));
        }
        if (onModified != null) {
            onModified.accept(this);
        }
        return old;
    }

    @Override
    public V remove(Object key) {
        V remove = map.remove(key);
        if (onRemove != null) {
            onRemove.accept(new Pair<>((K) key, remove));
        }

        if (onModified != null) {
            onModified.accept(this);
        }
        return remove;
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        map.putAll(m);

        if (onPutAll != null) {
            onPutAll.accept(m);
        }

        if (onModified != null) {
            onModified.accept(this);
        }
    }

    @Override
    public void clear() {
        map.clear();
        if (onClear != null) {
            onClear.accept(null);
        }

        if (onModified != null) {
            onModified.accept(this);
        }
    }

    @Override
    public Set<K> keySet() {
        return map.keySet();
    }

    @Override
    public Collection<V> values() {
        return map.values();
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        return map.entrySet();
    }

    public static final class Builder<K, V> {
        private Map<K, V> map;
        private Consumer<Map<K, V>> onInit;
        private Consumer<ModifyHookMap<K, V>> onModified;
        private Consumer<Triple<K, V, V>> onPut;
        private Consumer<Pair<K, V>> onRemove;
        private Consumer<Map<? extends K, ? extends V>> onPutAll;
        private Consumer<Void> onClear;

        public Builder() {
        }

        public Builder<K, V> map(Map<K, V> val) {
            map = val;
            return this;
        }

        public Builder<K, V> onInit(Consumer<Map<K, V>> val) {
            onInit = val;
            return this;
        }

        public Builder<K, V> onModified(Consumer<ModifyHookMap<K, V>> val) {
            onModified = val;
            return this;
        }

        public Builder<K, V> onPut(Consumer<Triple<K, V, V>> val) {
            onPut = val;
            return this;
        }

        public Builder<K, V> onRemove(Consumer<Pair<K, V>> val) {
            onRemove = val;
            return this;
        }

        public Builder<K, V> onPutAll(Consumer<Map<? extends K, ? extends V>> val) {
            onPutAll = val;
            return this;
        }

        public Builder<K, V> onClear(Consumer<Void> val) {
            onClear = val;
            return this;
        }

        public ModifyHookMap<K, V> build() {
            if (map == null) {
                throw new IllegalArgumentException("map 不能为空");
            }
            return new ModifyHookMap<K, V>(this);
        }
    }
}

测试代码如下(时间仓促,这里不使用 JUnit 单测,简单使用 main方法):

import com.alibaba.fastjson.JSON;
import com.google.common.collect.ImmutableList;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Demo {

    private static List<Integer> cache = new ArrayList<Integer>();
    private static ModifyHookMap<String, Integer> mapWrapper = new ModifyHookMap
            .Builder<String, Integer>()
            .map(new HashMap<>())
            .onInit((Map<String, Integer> map) -> {
                System.out.println("执行创建, map:" + JSON.toJSONString(map));
            })
            .onClear((v) -> {
                System.out.println("执行 clear");
            })
            .onRemove((Pair<String, Integer> pair) -> {
                System.out.println("执行 remove,k:" + pair.getKey() + ";v" + pair.getValue());
            })
            .onPut((Triple<String, Integer, Integer> triple) -> {
                System.out.println("执行 put,k:" + triple.getFirst() + ";v new:" + triple.getSecond() + ",v old:" + triple.getThird());
            })
            .onPutAll((Map<? extends String, ? extends Integer> map) -> {
                System.out.println("执行 putAll:" + JSON.toJSONString(map));
            })
            .onModified((modifyInvokeMap -> {
                cache = ImmutableList.copyOf(modifyInvokeMap.values());
                System.out.println("执行 修改操作" + JSON.toJSONString(cache));
            }))
            .build();


    public static void main(String[] args) {

        mapWrapper.put("1", 1);
        mapWrapper.put("1", 3);
        Map<String, Integer> otherMap = new HashMap<>();
        otherMap.put("2", 2);
        mapWrapper.putAll(otherMap);

        mapWrapper.remove("2");

        mapWrapper.clear();

    }
}



输出:

执行创建, map:{}
执行 修改操作[]
执行 put,k:1;v new:1,v old:null
执行 修改操作[1]
执行 put,k:1;v new:3,v old:1
执行 修改操作[3]
执行 putAll:{“2”:2}
执行 修改操作[3,2]
执行 remove,k:2;v2
执行 修改操作[3]
执行 clear
执行 修改操作[]

需要注意的是,此时 Consumer 里面如果传入的 key 或者 value 进行修改会影响到底层 Map 的数据。

因此,只建议在 Consumer 里执行读操作,不要执行修改操作。

三、总结

本文通过借鉴 SynchronizedList 实现了自己的带回调的 Map

想表达的是,开源代码不是万能的,当开源代码无法满足时,有时候需要我们自己编写特定逻辑来满足业务需求。

此外,学习的价值在于 学以致用,我们要尝试将所需知识灵活运用来解决业务问题,才是能真正发挥读源码的价值。

如果你有更好的处理方式,欢迎评论和我讨论。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明明如月学长

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值