Guava在项目中的实践

376 阅读5分钟

在大家平时实际的开发中,是否会觉得JDK中有些类提供的方法不够友好,而且经常会发现缺少自己想要的功能函数。这个时候就要引出我们的主角了--Guava。

Guava是google开源的一些java工具包,里面会有一些在平时开发过程需要用到的utils。说到utils就一定会想到另外一个非常有名的common-lang包,从我使用的感受来讲,Guava的实现的更加优雅。如果你使用了Guava,你会发现,真香。

下面我将分享我们在工程实践中发现的特别好用的工具。


Lists.transform函数

经常看到有些原来的业务代码会这样写:

    // List<A> list1
    List<String> list2 = new ArrayList<>(list1.size());
    for(A a: list1){
        list2.add(a.getId())
    }
    doSomething(list2)

为了组装一个需要调用方法的参数,需要先循环一次列表取出其中的一个字段放到新的List中,原来的工程有很多这样的代码,你会觉得特别特别的不优雅。刚开始我用同样的方式写了一个公用方法来代替原来的方式。后来觉得还是不够优雅,因为这里多循环了一次列表。有人可能会说List最终还是会被遍历的,参数转换的for循环并不会提升时间复杂度其实影响不大。是的,时间复杂度并没有增加,但是新的list2会增加空间复杂度。

后面我发现了guava的Lists.transform函数,从参数看起来和我自己写的公用方法差不多。

    List<String> list2 = Lists.transform(list1, A::getId);

但是,重点来了,我们看看Lists.transform的源码:

public static <F, T> List<T> transform(List<F> fromList, Function<? super F, ? extends T> function) {
    return (fromList instanceof RandomAccess)
        ? new TransformingRandomAccessList<F, T>(fromList, function)
        : new TransformingSequentialList<F, T>(fromList, function);
  }

在Guava的源代码中只是创建了一个新的List,我们再看一下TransformingRandomAccessList的实现:

 private static class TransformingRandomAccessList<F, T> extends AbstractList<T> implements RandomAccess, Serializable {
    final List<F> fromList;
    final Function<? super F, ? extends T> function;
    
    TransformingRandomAccessList(List<F> fromList, Function<? super F, ? extends T> function) {
      this.fromList = checkNotNull(fromList);
      this.function = checkNotNull(function);
    }
    @Override public void clear() {
      fromList.clear();
    }
    @Override public T get(int index) {
      return function.apply(fromList.get(index));
    }
    @Override public Iterator<T> iterator() {
      return listIterator();
    }
    @Override public ListIterator<T> listIterator(int index) {
      return new TransformedListIterator<F, T>(fromList.listIterator(index)) {
        @Override
        T transform(F from) {
          return function.apply(from);
        }
      };
    }
    @Override public boolean isEmpty() {
      return fromList.isEmpty();
    }
    @Override public T remove(int index) {
      return function.apply(fromList.remove(index));
    }
    @Override public int size() {
      return fromList.size();
    }
    private static final long serialVersionUID = 0;
 }

你会发现Guava其实是采用了装饰器模式去实现对象转换功能,function.apply(from)只有在迭代的时候才会被真正的执行。这个实现的方式非常的巧妙,减少了空间复杂度。

一定要谨记,这种实现方式也是有一定的弊端,不熟悉的人会容易让程序产生bug。因为最终返回的list2是个装饰对象,list2内部引用了原来的对象list1,list1和list2会互相影响,比如你删除转换后的list2的第一个元素就会通过调用TransformingRandomAccessList.remove函数把原来的list1的第一个元素删除了。

Multimap集合

在实际业务中,经常会遇到遇到一个key对应多个value的的场景,在没用guava之前,我们使用Map<Object, List<Object>>来存储数据。

    // List<A> list1
    Map<String, List<A>> map = new HashMap<>();
    for(A a: list1){
        List<A> valueList = map.getOrDefault(a.getId(), new ArrayList<>());
        valueList.add(a)
        map.put(a, valueList);
    }

上面是一个典型的从List转换为Map<String,List<Object>>的逻辑。使用Multimap来存储这种结构的数据使用起来会更加的便捷,想把List转换为Multimap可以直接使用Multimaps.index函数。

    Multimap<String, A> resultMap = Multimaps.index(list1, A::getId);

Multimap还定义了很多方便的操作。

public interface Multimap<K, V> {

  // 返回大小
  int size();
  
  //是否为空
  boolean isEmpty();
  
  // 是否包含某个key
  boolean containsKey(@Nullable Object key);
  
  // 是否包含某个value
  boolean containsValue(@Nullable Object value);
  
  // 是否存在这样的键值对组合
  boolean containsEntry(@Nullable Object key, @Nullable Object value);
  
  // 根据key添加一个value
  boolean put(@Nullable K key, @Nullable V value);
  
  // 根据key删除value集合中的一个value
  boolean remove(@Nullable Object key, @Nullable Object value);
  
  // 为一个key添加多个对应value
  boolean putAll(@Nullable K key, Iterable<? extends V> values);
  
  // 合并另一个Multimap
  boolean putAll(Multimap<? extends K, ? extends V> multimap);
  
  // 替换key对应的value集合
  Collection<V> replaceValues(@Nullable K key, Iterable<? extends V> values);
  
  // 删除一个key对应的所有value
  Collection<V> removeAll(@Nullable Object key);
  
  void clear();
  
  // 获取key对应的集合
  Collection<V> get(@Nullable K key);
  
  // 返回不会重复的key集合
  Set<K> keySet();
  
  // 返回Multiset,可以知道一个key对应几个value
  Multiset<K> keys();
  
  // 返回所有value的集合
  Collection<V> values();
  
  // 返回所有entry的集合
  Collection<Map.Entry<K, V>> entries();
  
  // 转换Multimap成Map<K, Collection<V>>
  Map<K, Collection<V>> asMap();
}

Multimap在guava中有多种实现,我们可以根据自己的需要创建相应的实例。

Cache缓存

在计算机中缓存是一个经常会提到的概念。很多项目中会使用redis作为一个远程缓存,可是远程缓存经过网络传输会降低访问速度,为了用户的极致体验我们会维护一个本地缓存降低接口RT。

我们可以使用Guava的Cache作为本地缓存。

    
    Cache<Object, Object> guavaCache = CacheBuilder.newBuilder();
        // 设置缓存失效时间为十分钟
        .expireAfterAccess(10,TimeUnit.MINUTES)
        // 设置缓存大小为1000
        .maximumSize(1000)
        // 设置value为虚引用
        .weakValues()
        // 设置value为弱引用
        .softValues()
        .build();
    guavaCache.put("key", "value");

Guava的Cache的缓存淘汰策略是LRU(最近最少访问淘汰策略),如果是缓存大小超过1000将会根据最近最少淘汰策略淘汰一个key,放入新的key和value。所以我们就放心的往cache里面放一些需要缓存的对象,不需要担心cache会把内存撑爆了。

最后

Guava还提供了一些集合类,反射操作工具,io类,optional类等,你值得尝试。