在大家平时实际的开发中,是否会觉得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类等,你值得尝试。