Collections(九)Guava Collections
Java Collections Framework基本介绍完了,当我们遇到性能或者有需需要定义自己的集合时,比如希望数据不是保存在内存中而是从数据库迭代,可以扩展JDK提供的明确设计的抽象实现AbstractXXX接口。
Guava提供了一些特别的集合实现和工具类,本文旨在入门Guava Collections,分为三个部分:
- 更多的集合类型
- 不可变集合
- 工具
详细文档请参阅官方文档:https://github.com/google/guava/wiki。
更多的集合类型
Multiset
Multiset是一个元素可以重复的集合,Multiset可以被看做一个无需关心顺序ArrayList,或者一个元素和元素个数的Map。如果没有这个类,我们通常的实现会是这样的:
new ArrayList<E>()
或者
new HashMap<E, Integer>
Multiset 继承了JDK Collection接口,并且提供了一些额外的方法。
| 方法 | 作用 |
|---|---|
| int count(@Nullable @CompatibleWith("E") Object element) | 获取元素的个数 |
| int setCount(E element, int count); | 设置元素的个数 |
| int add(@Nullable E element, int occurrences); | 新增元素 |
| Set<E> elementSet(); | 获取不同的元素集合 |
根据不同特性和实现,Guava提供了以下几种Multiset:
- HashMultiset
- LinkedHashMultiset
- TreeMultiset
- ConcurrentHashMultiset
- ImmutableMultiset
因为泛型书写的原因,Guava中创建集合大多数采用了工厂方法,不建议用new的方式,所以我们创建一个Multiset的代码如下:
Multiset<String> set = HashMultiset.<String>create();
Multimap
Multimap是一个key值允许重复的集合,可以看作为Map<K, List<V>>。如果没有这个类,我们的实现可能是这样的:
new HashMap<K, Collection<V>>
Multimap是一个独立的接口,它不是一个Ma,提供了一些基础方法:
| 方法 | 作用 |
|---|---|
| Collection<V> get(@Nullable K key); | 获取key对应的值集合 |
| Multiset<K> keys(); | 获取所有可重复的keys |
| Map<K, Collection<V>> asMap();转换为Map |
如果当你希望使用key-collection这样的结构时,就可以考虑使用Multimap,根据valus的实现方式不同,我们更多用的是接口ListMultimap和接口SetMultimap,前者返回的值是一个列表List<V> get(@Nullable K key);,后者返回的是一个set:Set<V> get(@Nullable K key);。
我们通过构建器MultimapBuilder进行初始化:
ListMultimap<String, String> map = MultimapBuilder.hashKeys().arrayListValues().build();
BiMap
BiMap是一个双向Map,既可以key-value映射,也可以value-key映射(调用inverse()方法),所以要求value必须是唯一的,实现原理是内部维护了两张哈希表。具体实有以下几个:
- HashBiMap
- EnumBiMap
- EnumHashBiMap
- ImmutableBiMap 同样,当我们初始化这些类时,是通过静态方法而不是new:
BiMap<String, Integer> userId = HashBiMap.create();
ClassToInstanceMap
ClassToInstanceMap一个专用的实现,表示类型与实例的映射,继承Map<Class<? extends B>, B>,我们可以看看接口定义:
public interface ClassToInstanceMap<B> extends Map<Class<? extends B>, B> {
<T extends B> T getInstance(Class<T> type);
<T extends B> T putInstance(Class<T> type, @Nullable T value);
}
Guava提供了两个实现MutableClassToInstanceMap和ImmutableClassToInstanceMap,同样通过静态方法初始化:
MutableClassToInstanceMap.create();
不可变集合
顾名思义,该集合不支持修改操作,比如:
- ImmutableList
- ImmutableSet
- ImmutableMap
- ImmutableMultiset
- ImmutableMultimap
- ImmutableBiMap
通过前面文了解过,JDK提供了一个包装方法返回一个不可修改的集合:Collections.unmodifiableXXX,那么为什么Guava提供了新的实现方式呢?主要基于以下几点来考虑:
- 需要一个集合的常量的场景是广泛的,如果都要通过包装方法,显得冗余且厌烦
- 包装方法通常需要先实例化一个集合,而这个集合的引用如果继续被操作将影响到不可变的集合
- 包装方法是低效的,背后调用了一个具体集合的方法,里面可能包含了很多在不可变集合中无用的代码,所以我们需要针对不可变写一个高效的代码。
注意:所有不可变集合不支持null值,因为google觉得大多数场景下都不会使用null值。 构造不可变集合的方式也是通过工厂方法或者构建器:
Builder<String> builder = ImmutableList.<String>builder();
builder.add(str);
ImmutableList<String> list = builder.build();
// of
ImmutableList<String> list2 = ImmutableList.of("hi");
// copy of
ArrayList<Object> arrayList = new ArrayList<>();
arrayList.add("hi");
ImmutableList<Object> copyOf = ImmutableList.copyOf(arrayList);
工具
正如JDK提供了Collections工具类一样,Guava也提供了一系列工具类。
- Collections2 区别于JDK工具类
- Lists
- Sets
- Maps
这里不作介绍,可以自行查看官方文档,值得注意的是,这些工具类提供的静态构造方式Lists.newArrayList()已经不建议使用了,官方建议jdk1.7以后使用<>语法来代替这些工厂方法。
装饰器模式
Guava提供了一系列ForwardingXXX类为我们对集合进行装饰提供了便捷,我们只需要继承这些类,然后实现抽象方:
protected abstract List<E> delegate();
接下来,你只需要重写对应的方法进行装饰了。
抽象迭代器
我们知道,实现Iterator接口需要实现几个方法,guava为我们封装了一个实现Iterator接口的抽象类AbstractIterator方便我们继承,我们只需要方法computeNext即可,下面这段代码实现了一个迭代字符串的列表。
package com.deepoove.datastructure;
import java.util.AbstractList;
import java.util.Iterator;
import java.util.Objects;
import com.google.common.collect.AbstractIterator;
public class CharacterList extends AbstractList<Character> {
private transient String str;
public CharacterList(String str) {
Objects.requireNonNull(str);
this.str = str;
}
@Override
public Character get(int index) {
return str.charAt(index);
}
@Override
public int size() {
return str.length();
}
@Override
public Iterator<Character> iterator() {
return new AbstractIterator<Character>() {
int i = 0;
@Override
protected Character computeNext() {
if (i == CharacterList.this.size()) return endOfData();
return CharacterList.this.str.charAt(i++);
}
};
// return new Iterator<Character>() {
//
// int i = 0;
//
// @Override
// public boolean hasNext() {
// return i < CharacterList.this.size();
// }
//
// @Override
// public Character next() {
// return CharacterList.this.str.charAt(i++);
// }
// };
}
}
总结
虽然JDK也能完某些任务,但是Guava Collections提供了很大的便利性。
这是这个系列文的最后一篇,熟读并不是需要你记得这些内容,而是更好的使用它。