《Guava学习笔记之二》:新集合类型
先看一张Java和Guava的集合对比图,如下:(图截取于http://ifeve.com/google-guava-immutablecollections/)
在上篇博文中,我们了解了不可变集合,例如:ImmutableSet、ImmutableList等。
这篇博文,就来看下Multiset、MultiMap、BiMap、Table等。
1、MultiSet
我们都知道在Java中Set集合是不接受重复的元素的哈。但是
MultiSet:接受重复值的Set,即可以多次添加相等的元素
Multiset继承自JDK中的Collection接口,而不是Set接口,所以包含重复元素并没有违反原有的接口契约。
可以用两种方式看待Multiset:
1、没有元素顺序限制的ArrayList
2、Map<E, Integer>
,键为元素,值为计数
Guava的Multiset API也结合考虑了这两种方式:
当把Multiset看成普通的Collection时,它表现得就像无序的ArrayList:
- 1、add(E)添加单个给定元素
- 2、iterator()返回一个迭代器,包含Multiset的所有元素(包括重复的元素)
- 3、size()返回所有元素的总个数(包括重复的元素)
当把Multiset看作Map
@Test
public void testMultiSet(){
Set<String> set = Sets.newHashSet("a","b","c");
Multiset<String> multiset = HashMultiset.create();
multiset.addAll(set);
multiset.add("a");
System.out.println(multiset);
Set<String> keys = multiset.elementSet();
for(String key:keys){
System.out.println(key+", count:" +multiset.count(key));
}
}
输出:
[a x 2, b, c]
a, count:2
b, count:1
c, count:1
2、MultiMap
每个有经验的Java程序员都在某处实现过Map<K, List<V>>或Map<K, Set<V>>
,并且要忍受这个结构的笨拙。例如,Map<K, Set<V>>
通常用来表示非标定有向图。Guava的 Multimap可以很容易地把一个键映射到多个值。换句话说,Multimap是把键映射到任意多个值的一般方式。
可以用两种方式思考Multimap的概念:”键-单个值映射”的集合:
a -> 1 a -> 2 a ->4 b -> 3 c -> 5
或者”键-值集合映射”的映射:
a -> [1, 2, 4] b -> 3 c -> 5
一般来说,Multimap接口应该用第一种方式看待,但asMap()视图返回Map<K, Collection<V>>
,让你可以按另一种方式看待Multimap。重要的是,不会有任何键映射到空集合:一个键要么至少到一个值,要么根本就不在Multimap中。
很少会直接使用Multimap接口,更多时候你会用ListMultimap或SetMultimap接口,它们分别把键映射到List或Set。
Multimap.get(key)以集合形式返回键所对应的值视图,即使没有任何对应的值,也会返回空集合。ListMultimap.get(key)返回List,SetMultimap.get(key)返回Set。
Multimap的视图
Multimap还支持若干强大的视图:
1、asMap为Multimap<K, V>
提供Map<K,Collection<V>>
形式的视图。返回的Map支持remove操作,并且会反映到底层的Multimap,但它不支持put或putAll操作。更重要的是,如果你想为Multimap中没有的键返回null,而不是一个新的、可写的空集合,你就可以使用asMap().get(key)。(你可以并且应当把asMap.get(key)返回的结果转化为适当的集合类型——如SetMultimap.asMap.get(key)的结果转为Set,ListMultimap.asMap.get(key)的结果转为List.
2、entries用Collection<Map.Entry<K, V>>
返回Multimap中所有”键-单个值映射”——包括重复键。(对SetMultimap,返回的是Set)
3、keySet用Set表示Multimap中所有不同的键。
4、keys用Multiset表示Multimap中的所有键,每个键重复出现的次数等于它映射的值的个数。可以从这个Multiset中移除元素,但不能做添加操作;移除操作会反映到底层的Multimap。
5、values()用一个”扁平”的Collection包含Multimap中的所有值。这有一点类似于Iterables.concat(multimap.asMap().values())
,但它直接返回了单个Collection,而不像multimap.asMap().values()
那样是按键区分开的Collection。
看一个例子:
@Test
public void testMultimap(){
Multimap<String,Integer> multimap = ArrayListMultimap.create();
multimap.put("a", 1);
multimap.put("a", 2);
multimap.put("b", 3);
multimap.put("c", 4);
System.out.println(multimap);
System.out.println(multimap.size());//所有元素的个数
System.out.println(multimap.keySet().size());//不同的key的个数
//检查multimap是否有"x"这个键
String key ="x";
//这个检查就说明了即使multimap中不含key,也不是返回null,而是返回的是一个空集合
if(multimap.get(key)!=null){
System.out.println("multimap中含有key:"+key);
}
//asMap的使用
Map<String,Collection<Integer>> map = multimap.asMap();
if(map.get(key)==null){//这里可以直接用 map.containsKey(key)
System.out.println("multimap中不含有key:"+key);
}
}
运行结果:
{a=[1, 2], b=[3], c=[4]}
4
3
multimap中含有key:x
multimap中不含有key:x
Multimap不是Map,Multimap<K, V>
不是Map<K,Collection<V>>
,虽然某些Multimap实现中可能使用了map。它们之间的显著区别包括:
1、Multimap.get(key)总是返回非null、但是可能空的集合。这并不意味着Multimap为相应的键花费内存创建了集合,而只是提供一个集合视图方便你为键增加映射值(注:如果有这样的键,返回的集合只是包装了Multimap中已有的集合;如果没有这样的键,返回的空集合也只是持有Multimap引用的栈对象,让你可以用来操作底层的Multimap。因此,返回的集合不会占据太多内存,数据实际上还是存放在Multimap中)。如果你更喜欢像Map那样,为Multimap中没有的键返回null,请使用asMap()视图获取一个Map<K, Collection<V>>
。(或者用静态方法Multimaps.asMap()
为ListMultimap返回一个Map<K, List<V>>
。对于SetMultimap和SortedSetMultimap,也有类似的静态方法存在)
2、当且仅当有值映射到键时,Multimap.containsKey(key)
才会返回true。尤其需要注意的是,如果键k之前映射过一个或多个值,但它们都被移除后,Multimap.containsKey(key)
会返回false。
3、Multimap.entries()
返回Multimap中所有”键-单个值映射”——包括重复键。如果你想要得到所有”键-值集合映射”,请使用asMap().entrySet()。
4、Multimap.size()
返回所有”键-单个值映射”的个数,而非不同键的个数。要得到不同键的个数,请改用Multimap.keySet().size()
。
关于更多guava新的集合可以看这里:http://ifeve.com/google-guava-newcollectiontypes/。
小结
关于这些新的集合,只需要知道有这些东西就可以了,虽然有一些注意事项,但是在我们使用的时候,我们只需要查阅下资料和API就可以很快的上手使用。