Guava是一组来自Google的核心Java库,在实际应用中非常广泛,熟练掌握guava可以让同学们在开发中如虎添翼,节省开发时间,提高工作效率。
Guava 的好处:
本文将详细介绍与MultiSet相关的类,即guava中的MultiMap。
首先在maven工程中需要引入guava包,
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
MultiMap是一个映射,但它可以根据一个键映射多个值。当我们遇到具有多个值的场景时,我们必须编写大量代码来维护列表。
示例:我们在列表中有一个水果名称列表。我们想要构造一个指向水果名称列表的字符映射,以将字符映射到以该字符开头的水果列表。通常我们是这样做的:
List<String> fruits = Arrays.asList("apple", "banana", "orange", "avocado");
Map<Character, List<String>> map = new HashMap<>();
for (String fruit : fruits) {
char firstChar = fruit.charAt(0);
if (!map.containsKey(firstChar)) {
map.put(firstChar, new ArrayList<>());
}
map.get(firstChar).add(fruit);
}
System.out.println(map); //{a=[apple, avocado], b=[banana], o=[orange]}
与上述方案相比,我们可以做得更好。我们可以使用 computeIfAbsent
方法来初始化每个新字符的空列表。
for (String fruit : fruits) {
char firstChar = fruit.charAt(0);
map.computeIfAbsent(firstChar, c -> new ArrayList<>())
.add(fruit);
}
Google Guava提供的MultiMap能让我们通过简化且功能强大的API做到这一点,
MultiMap接口有两个类型参数 K 和 V。 该值并未显示的写入为集合。 当我们编写 Multimap<Integer, String>
时,值的类型是一个字符串集合。
本文将使用HashMultimap
来演示 Multimap 的 API。当我们进入本文的最后一部分时,我们会看到Multimap的其他实现类。
MultiMap 添加数据
MultiMap接口提供了两种向MultiMap添加项目的方法 - put 和 putAll 方法。
SetMultimap<String, String> multimap = HashMultimap.create();
multimap.put("1", "a");
multimap.put("2", "b");
multimap.put("2", "c");
System.out.println(multimap);
输出:
{1=[a], 2=[b, c]}
键 1 具有值 a,键 2 具有值 b 和 c。我们可以通过传递可迭代的值来使用 putAll 为一个键添加多个值。
multimap.putAll("3", Arrays.asList("d", "e"));
System.out.println(multimap);
//{1=[a], 2=[b, c], 3=[d, e]}
MultiMap 遍历数据
我们可以使用 forEach 方法打印MultiMap。采用一个BiConsumer
函数来接受键值和值。
multimap.forEach((k, v) -> System.out.println(k + " = " + v));
输出:
1 = a
2 = b
2 = c
3 = d
3 = e
查询方法
get 方法获取映射到键的值, 对应类型为 Set
。
System.out.println(multimap.get("1")); //[a]
System.out.println(multimap.get("2")); //[b, v]
System.out.println(multimap.get("22")); //[]
当查询不存在的键时,它将返回一个空集(而不是 null)。
如果multimap包含了值,则 containsValue
方法返回 true。换句话说,如果多映射包含至少一个具有此值的键值对,则返回 true。
System.out.println(multimap.containsValue("c")); //true
System.out.println(multimap.containsValue("f")); //false
如果multimap至少有一个具有传递的键和值的键值对,则 containsEntry
方法返回 true。
System.out.println(multimap.containsEntry("2", "c")); //true
System.out.println(multimap.containsEntry("2", "a")); //false
移除方法
为了演示删除方法,这里将从原始内容创建一个新的Multimap,并从中删除元素以防止原始数据发生变化。
remove 方法采用一个键和一个值,并删除单个键值对。如果有多个这样的键值对,它将删除其中一个(无法指定删除哪一个)。
removeAll 采用一个键,并删除映射到该键的所有值。除此之外,它还返回删除的值的列表。
SetMultimap copy = HashMultimap.create(multimap);
copy.remove("1", "a");
System.out.println(copy); //{2=[b, c], 3=[d, e]}
copy.removeAll("2");
System.out.println(copy); //{3=[d, e]}
替换值
此方法(replaceValues
)允许我们替换键的值。它返回该键的旧/现有值。
System.out.println(multimap.replaceValues("3", Arrays.asList("d1", "e1"))); //[d, e]
System.out.println(multimap); //{1=[a], 2=[b, c], 3=[e1, d1]}
首先,键 3 具有值 d 和 e。我们将其替换为 d1 和 e1。下一个打印语句确认当前内容.
查看方法
multimap提供了多种查看方法。
keys
: 将multimap中每个键值对中的键作为多集返回。因此,它可以使一个键重复多次。其大小与multimap的大小相同。
System.out.println(multimap.keys()); //[1, 2 x 2, 3 x 2]
如果它存在多次(映射多个值),则显示为 <键> x <映射数>
keySet
: 它返回所有不同键的视图集合。因此,返回类型是一个集合。
System.out.println(multimap.keySet()); //[1, 2, 3]
values
: 它返回所有键值对的所有值的集合的视图。返回的集合的大小与multimap的大小相同。
System.out.println(multimap.values()); //[a, b, c, e1, d1]
asMap
: 它将multimap的视图作为传统map返回。
System.out.println(multimap.asMap());//{1=[a], 2=[b, c], 3=[e1, d1]}
在本节中,我们将介绍 Multimaps 类中的静态方法。
创建不可修改的multimap
我们可以通过调用unmodifiableMultimap
方法使现有的 multimap 不可修改。multimap 子接口有特定的方法,如 SetMultimap
,ListMultimap
和SortedSetMultimap
multimap。
如果我们有一个SetMultimap,我们可以创建一个不可修改的multimap。,
SetMultimap<String, String> multimap = HashMultimap.create();
multimap.put("1", "a");
multimap.put("2", "b");
multimap.put("3", "c");
SetMultimap<String, String> unmodifiableMultimap = Multimaps.unmodifiableSetMultimap(multimap);
尝试对返回的multimap添加数据将引发 UnsupportedOperationException
,因为它是不可变的。
转换值和条目
multimap有一种方便的方法,即transformValues
,将值转换为其他值。例如,使用上面创建的multimap ,让我们通过将值作为后缀添加的方式来转换值。
Multimap<String, String> transformedMultimap = Multimaps.transformValues(multimap, value -> value + "-" + value);
System.out.println(transformedMultimap);//{1=[a-a], 2=[b-b], 3=[c-c]}
所以现在,值 a 变为 a-a。
方法transformValues
将multimap作为第一个参数,将函数作为第二个参数。该函数将通过传递multimap中的每个值来调用,并且函数的输出将用作新值。
请注意,此方法返回传递的multimap的视图,因此延迟应用该函数。因此,在执行任何查询操作(如包含值)时,可以多次应用该函数。如果我们需要经常使用结果并希望避免函数计算,我们可以将结果复制到新的多映射中。
如果我们想使用键值对的键进行值转换,我们可以使用transformEntries
。第二个参数的类型为EntryTransformer
,它采用键值对并返回新值。同样,返回的multimap只是一个视图。
transformedMultimap = Multimaps.transformEntries(multimap, (k, v) -> k + "-" + v);
System.out.println(transformedMultimap);//{1=[1-a], 2=[2-b], 3=[3-c]}
使用原始的multimap,我们将值设置为键和值的串联。
索引方法
索引方法采用 Iterable 和函数,用于构造新的multimap(而不是视图)。该函数将传递列表中的每个值,该函数的结果将成为multimap的键。传递给函数的元素本身将是值。
示例:我们有一个水果清单。我们想要构建一个以水果的名称首字母为key的multimap,我们可以这样做,如下所示:
List<String> list = Arrays.asList("apple", "banana", "orange", "avocado");
Multimap<Character, String> constructedMultimap = Multimaps.index(list, string -> string.charAt(0));
System.out.println(constructedMultimap);//{a=[apple, avocado], b=[banana], o=[orange]}
从注释中显示的输出中,水果苹果和avacado映射到键a。请记住,我们必须编写5-6行代码(在本文开头)才能做同样的事情,
让我们看另一个例子。我们将创建从单词长度到具有该长度的水果的映射。
Multimap<Integer, String> lengthToFruitMultimap = Multimaps.index(list, String::length);
System.out.println(lengthToFruitMultimap);//{5=[apple], 6=[banana, orange], 7=[avocado]}
过滤
我们可以从满足谓词的多地图过滤条目中创建视图。有三种方法 - filterKeys
, filterValues
and filterEntries
。这使我们能够分别指定键,值或条目(键和值)的过滤条件。 让我们看一个例子。我们将使用上面构建的lengthToFruitMultimap
。首先,让我们过滤并仅获取奇数长度的条目。
Multimap<Integer, String> oddLengthFruits = Multimaps.filterKeys(lengthToFruitMultimap, k -> k % 2 == 1);
System.out.println(oddLengthFruits); //{5=[apple], 7=[avocado]}
现在,让我们获取名字以元音开头的水果。
private static Predicate<String> startsWithVowel() {
List<String> vowels = Arrays.asList("a", "e", "i", "o", "u");
return str -> vowels.stream().anyMatch(vowelCharacter -> str.startsWith(vowelCharacter));
}
Multimap<Integer, String> fruitsStartingWithAVowelMultimap =
Multimaps.filterValues(lengthToFruitMultimap, startsWithVowel());
System.out.println(fruitsStartingWithAVowelMultimap);//{5=[apple], 6=[orange], 7=[avocado]}
请注意,有两个水果映射到键 6(orange and banana)。但只有一个(orange)以元音开头。 让我们将这两者结合起来,并过滤其键长度为奇数且值以元音开头的条目。
Multimap<Integer, String> resultMultimap =
Multimaps.filterEntries(lengthToFruitMultimap, (entry) -> entry.getKey() % 2 == 1
&& startsWithVowel().test(entry.getValue()));
System.out.println(resultMultimap);//{5=[apple], 7=[avocado]}
建议不要直接使用Multimap, 而是使用其中一个子接口(如SetMultimap
或ListMultimap
)。现在,我们将看一些实现Multimap的类。
它使用哈希表(哈希映射)实现Multimap。因此,它不保证按键或映射到键的值之间的顺序。它也不允许键的重复值(重复的键值对)。换句话说,我们只能为一个键添加一次值。
SetMultimap<String, String> hashMultimap = HashMultimap.create();
hashMultimap.put("2", "d");
hashMultimap.put("1", "a");
hashMultimap.put("1", "c");
hashMultimap.put("1", "b");
hashMultimap.put("1", "b");
System.out.println(hashMultimap);
输出:
{1=[a, b, c], 2=[d]}
尽管我们将键 2 添加到 1 之前,但由于它使用的是 HashMap,因此输出不保证排序。在这里,我们在键2之前获得键1的条目。将值 b 相加两次不会将其相加两次,因为它使用 HashSet 作为值。
此multimap的实现基于键的已链接哈希映射和值的已链接哈希集。因此,它保留了插入顺序,但不允许重复的键值对。
SetMultimap<String, String> linkedHashMultimap = LinkedHashMultimap.create();
linkedHashMultimap.put("2", "d");
linkedHashMultimap.put("1", "a");
linkedHashMultimap.put("1", "c");
linkedHashMultimap.put("1", "b");
linkedHashMultimap.put("1", "b");
System.out.println(linkedHashMultimap);//{2=[d], 1=[a, c, b]}
我们可以看到为键(2 在 1 之前)和值(a,c,b)保留的插入顺序。
它对键使用HashMap,对值使用ArrayList。因此,它不维护键的插入顺序,但由于它对值使用 ArrayList,因此它保证插入顺序并且可以有重复项。
ListMultimap<String, String> arrayListMultimap = ArrayListMultimap.create();
arrayListMultimap.put("2", "d");
arrayListMultimap.put("1", "a");
arrayListMultimap.put("1", "c");
arrayListMultimap.put("1", "b");
arrayListMultimap.put("1", "b");
System.out.println(arrayListMultimap);//{1=[a, c, b, b], 2=[d]}
对键使用LinkedHashMap映射,对值使用 LinkedList。它按插入顺序存储数据(键和值),并且可以有重复项。
ListMultimap<String, String> linkedListMultimap = LinkedListMultimap.create();
linkedListMultimap.put("2", "d");
linkedListMultimap.put("1", "a");
linkedListMultimap.put("1", "c");
linkedListMultimap.put("1", "b");
linkedListMultimap.put("1", "b");
System.out.println(linkedListMultimap);//{2=[d], 1=[a, c, b, b]}
分别对键和值使用TreeMap和TreeSet。因此,它使用自然排序来对键进行排序,并使用映射到键的值进行排序。
SortedSetMultimap<String, String> treeMultimap = TreeMultimap.create();
treeMultimap.put("2", "d");
treeMultimap.put("1", "a");
treeMultimap.put("1", "c");
treeMultimap.put("1", "b");
treeMultimap.put("1", "b");
System.out.println(treeMultimap);//{1=[a, b, c], 2=[d]}
本文详细介绍了谷歌番Google Guava中的MultiMap。带大家学习了MultiMap接口的 API 或方法。然后学习了Multimaps类的一些实用方法。最后,我们看到了一些带有示例的Multimap实现类。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。