【新集合类型】
Guava在现有的JDK集合中有新增了Multiset、Multimap、BiMap、Table、ClassToInstanceMap、RangeSet、RangeMap等新集合。这些新集合能够跟JDK集合框架共存,并遵循JDK接口契约。所有新集合的构造器都进行了私有化处理,并通过静态函数生成实例。
一、Multiset
Multiset多用于统计元素个数,例如统计一个句子中相同字母的个数。传统的方法就是依次遍历存储在Map<String, Integer>中,最后迭代输出,而有了Multiset,只需将字母传入构造器中,就会自动统计出来。
Multiset<String> multiset = HashMultiset.create();
String string = "hello world";
multiset = TreeMultiset.create(Lists.newArrayList(string.split("")));
Set<String> strings = multiset.elementSet();
for (String s : strings) {
System.out.println(s + ": " + multiset.count(s));
}
// : 1
// d: 1
// e: 1
// h: 1
// l: 3
// o: 2
// r: 1
// w: 1
注:Lists为Guava封装的强大的集合工具类,第四章会详细介绍
来看下Multiset的常用方法
multiset.add("1"); multiset.add("1"); multiset.add("2");
multiset.add("3"); multiset.add("3"); multiset.add("1");
System.out.println(multiset.count("1")); // 3,字符串"1"的个数
System.out.println(multiset.size()); // 6,元素总数
System.out.println(multiset.elementSet()); // [1, 2, 3]
System.out.println(multiset.setCount("1", 2)); // 字符串"1"的个数设为 2
System.out.println(multiset.count("1")); // 2
System.out.println(multiset.size()); // 5
方法 | 描述 |
---|---|
count(E) | 给定元素在Multiset中的计数 |
elementSet() | Multiset中不重复元素的集合,类型Set<E> |
add(E, int) | 增加给定元素在Multiset中的计数 |
remove(E, int) | 减少给定元素在Multiset中的计数 |
setCount(E, int) | 设置给定元素在Multiset中的计数,不为负数 |
size() | 返回集合元素的总个数(包括重复的元素) |
entrySet() | 返回Set<Multiset.Entry<E>>,包含的Entry支持getElement()和getCount()方法 |
另外要注意这么几点:Multiset中的元素计数只能是正数;对于没有的元素,调用count(E)方法的返回值为0;size()方法返回的是元素总数,包括重复元素,对于不重复元素则使用elementSet().size()方法。
二、Mulitimap
Multimap对map提供了一对多的方式,也就是说一个键可对应多个值,相当于Map<K, List<V>>或Map<K, Set<V>>的格式,因此通过get(K)获得的结果是以集合的形式返回,即使没有对应的值,也会返回空集合,不存在null的情况。
关于Mulitimap的实现形式
Multimap<String, String> hashMultimap = HashMultimap.create();
hashMultimap.put("a", "1"); hashMultimap.put("a", "3");
hashMultimap.put("a", "2"); hashMultimap.put("a", "2");
hashMultimap.put("b", "1");
Multimap<String, String> arrayListMultimap = ArrayListMultimap.create();
arrayListMultimap.put("a", "1"); arrayListMultimap.put("a", "3");
arrayListMultimap.put("a", "2"); arrayListMultimap.put("a", "2");
arrayListMultimap.put("b", "1");
// HashMultimap 和 ArrayListMultimap 实现 Multimap
System.out.println(hashMultimap.get("a")); // 结果:[1, 2, 3]。Set形式,值不会有重复
System.out.println(hashMultimap.get("b")); // [1]
System.out.println(hashMultimap.get("1")); // 结果:[]。没有对应,返回空集合
System.out.println(arrayListMultimap.get("a")); // 结果:[1, 2, 2, 3]。ArrayList形式
System.out.println(arrayListMultimap.get("b")); // [1]
System.out.println(arrayListMultimap.asMap()); // 结果:{a=[1, 2, 2, 3], b=[1]}。提供 Map<K, Collection<V>> 的形式
System.out.println(hashMultimap.asMap().values()); // 结果:[[1, 2, 3], [1]]。Map<K, Collection<V>>的values形式
System.out.println(arrayListMultimap.values()); // 结果:[1, 2, 2, 3, 1]。Multimap<K, V>的values形式,将多个ArrayList<V>合为一个Collection
System.out.println(hashMultimap.values()); // 结果:[1, 2, 3, 1]。将多个Set<V>合为一个Collection
System.out.println(arrayListMultimap.keys()); // 结果:[a x 4, b]。keys()返回Multiset的类型
System.out.println(arrayListMultimap.keySet()); // [a, b]
关于Mulitimap的其他实现形式还有LinkedHashMultimap、TreeMultimap、ImmutableMultimap、ImmutableSetMultimap等,可以自行尝试,其中以Immutable开头的为不可变集合类型,第三章会有介绍。另外,除了不可变集合类型,其他实现形式都支持键值为null的情况
修改Multimap的方法
方法 | 描述 |
---|---|
put(K, V) | 添加键到单个值的映射 |
putAll(K, Iterable<V>) | 添加键到多个值的映射 |
remove(K, V) | 移除键到值的映射,如果有这样的键值并成功移除,返回true。 |
removeAll(K) | 清除键对应的所有值,返回的集合包含所有之前映射到K的值,但修改这个集合就不会影响Multimap了。 |
replaceValues(K, Iterable<V>) | 清除键对应的所有值,并重新把key关联到Iterable中的每个元素。返回的集合包含所有之前映射到K的值。 |
说白了,对Multimap的修改就是通过get()方法获取到集合,再进行add()或remove()操作。
三、BiMap
BiMap用于实现键值对的双向映射。当然也可以维护两个Map,但这样的操作太不方便了,而且极易出错。
HashBiMap的实现:
BiMap<String, Integer> biMap = HashBiMap.create();
biMap.put("a", 1);
biMap.put("b", 2);
// biMap.put("c", 1); // throw IllegalArgumentException: value already present: 1
biMap.forcePut("c", 1);
System.out.println(biMap); // 结果:{b=2, c=1}。之前a的键值被替换成了c
System.out.println(biMap.inverse()); // {2=b, 1=c}
从上例看出,当要将键映射到已存在的值上时,会抛IllegalArgumentException异常,但可以通过forcePut()强制替换,这样就可以保证键值的唯一性,在键值转换过程不会丢失数据或发生灵异事件。
BiMap的其他实现还有ImmutableBiMap、EnumBiMap、EnumHashBiMap,其中EnumBiMap的键值必须为枚举类型、EnumHashBiMap的键必须为枚举类型。
四、Table
第二节中的Multimap用于简化Map<K, Collection<V>>的形式,但对于Map<R, Map<C, V>>这种格式的就需要用到Table来简化了。
与Multimap类似,Table也有转化为Map<R, Map<C, V>>的方法,下面是HashBasedTable的实现例子:
Table<String, String, Integer> hashTable = HashBasedTable.create();
hashTable.put("0", "0", 0); hashTable.put("0", "1", 1);
hashTable.put("1", "0", 2); hashTable.put("1", "1", 3);
System.out.println(hashTable); // {0={0=0, 1=1}, 1={0=2, 1=3}}
System.out.println(hashTable.rowMap()); // {0={0=0, 1=1}, 1={0=2, 1=3}}
System.out.println(hashTable.cellSet()); // [(0,0)=0, (0,1)=1, (1,0)=2, (1,1)=3]
System.out.println(hashTable.row("1")); // {0=2, 1=3}
System.out.println(hashTable.values()); // [0, 1, 2, 3]
可以看出cellSet()方法是将Table以类似坐标的形式输出。
Table的其他实现形式:TreeBasedTable、ImmutableTable、ArrayTable。其中ArratTable在初始时需要指定行列大小,实现方式上与其他不同。
五、ClassToInstanceMap
ClassToInstanceMap是一种特殊的Map:它的键是类型,而值是符合键所指类型的对象。
ClassToInstanceMap<B>实现了Map<Class<? extends B>, B>的形式,即一个映射B的子类型到对应实例的Map。这里的B是Map所支持类型的上界,看例子来理解下:
ClassToInstanceMap<List> classToInstanceMap = MutableClassToInstanceMap.create();
classToInstanceMap.putInstance(ArrayList.class, Lists.newArrayList());
classToInstanceMap.putInstance(LinkedList.class, Lists.newLinkedList());
classToInstanceMap.putInstance(ArrayList.class, Lists.newArrayList(1, 2, 3));
System.out.println(classToInstanceMap.getInstance(ArrayList.class)); // [1, 2, 3]
System.out.println(classToInstanceMap); // {class java.util.LinkedList=[], class java.util.ArrayList=[1, 2, 3]}
ClassToInstanceMap只额外扩展了两个方法getInstance(Class<T>)和putInstance(Class<T>, T),用于类型的安全转换,避免强转导致的错误。
同样键是唯一的,当有重复的键存在时会覆盖前一个键的映射,且存放键的类型必须为List或List的子类。
ClassToInstanceMap的实现有 MutableClassToInstanceMap 和 ImmutableClassToInstanceMap。
六、RangeSet
RangeSet描述了一组不相连的、非空的区间。不只是对数字的操作,只要定义了排序规则都可以作为泛型放入进行比较。
RangeSet<Integer> rangeSet = TreeRangeSet.create();
rangeSet.add(Range.closed(1, 20)); // [1‥20]
rangeSet.remove(Range.closed(3, 11)); // [[1‥3), (11‥20]]
rangeSet.add(Range.openClosed(3, 5)); // [[1‥3), (3‥5], (11‥20]]
System.out.println(rangeSet.contains(11)); // false
System.out.println(rangeSet.rangeContaining(4)); // (3‥5]
System.out.println(rangeSet.span()); // [1, 20]
System.out.println(rangeSet); // [[1‥3), (3‥5], (11‥20)]
System.out.println(rangeSet.asRanges()); // [[1‥3), (3‥5], (11‥20)]
System.out.println(rangeSet.asDescendingSetOfRanges()); // [(11‥20), (3‥5], [1‥3)]
System.out.println(rangeSet.complement()); // [(-∞‥1), [3‥3], (5‥11], [20‥+∞)]
RangeSet常用方法:
方法 | 说明 |
---|---|
contains(C) | 判断任何区间是否有给定数值 |
rangeContaining(C) | 返回包含给定元素的区间,若没有这样的区间,返回null |
encloses(Range<C>) | 判断RangeSet中是否有任何区间包括给定区间 |
span() | 返回包括RangeSet中所有区间的最小区间。(注:官网文档上是这样写的,但上述实际结果跟说明不一致,看源码后实现的应该是所有区间中最小区间的下限和最大区间的上限结合的区间,也就是上述代码中1和20的区间范围) |
asRanges | 用Set<Range<C>>表现RangeSet,这样可以遍历其中的Range |
asDescendingSetOfRanges() | 所有区间倒叙排序 |
complement() | 返回RangeSet的补集 |
subRangeSet(Range<C>) | 返回RangeSet与给定Range的交集 |
在添加或删除区间中还用到了类Range下的静态方法,从字面意思上就是区间的开闭设置用途。
RangeSet的实现有 TreeRangeSet 和 ImmutableRangeSet。
七、RangeMap
RangeMap提供了不相交的、非空的区间到特定值的映射,和RangeSet不同的是RangeMap不会合并相邻的映射,即便相邻的区间映射到相同的值。
RangeMap<Integer, String> rangeMap = TreeRangeMap.create();
rangeMap.put(Range.closed(1, 10), "foo"); // [[1‥10]=foo]
rangeMap.put(Range.open(3, 6), "bar"); // [[1‥3]=foo, (3‥6)=bar, [6‥10]=foo]
rangeMap.put(Range.open(10, 20), "foo"); // [[1‥3]=foo, (3‥6)=bar, [6‥10]=foo, (10‥20)=foo]
rangeMap.remove(Range.closed(5, 11)); // [[1‥3]=foo, (3‥5)=bar, (11‥20)=foo]
RangeMap的实现有 TreeRangeMap 和 ImmutableRangeMap。