首先想一想当你在构建复杂 map
Map<K,Collection<V>>
时你是怎么做的?
一般场景是我们拿到一个 List
对其进行遍历分组操作。假如我们有一个学生成绩列表,现在需要根据得分进行分组,查找每个得分是哪些人。
用于演示的数据
代码语言:javascript复制/**
* 学生信息类
*
* @author 一个程序猿的异常
* @date 2021/09/17
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Student implements Serializable {
private Integer id;
private String name;
private Integer age;
private Integer score;
}
Java8之前的做法
- 初始化map
- 根据key获取value
- 判断value是否为空,为空初始化集合
- 将当前值存入集合
- 当前key&集合存入map
代码如下:
代码语言:javascript复制@Test
public void testSample() {
List<Student> students = new ArrayList<>();
students.add(new Student(1, "威震天", 200, 100));
students.add(new Student(2, "灭霸", 201, 90));
students.add(new Student(3, "奥特曼", 2000, 100));
students.add(new Student(4, "哥斯拉", 200, 80));
Map<Integer, List<Student>> scoreMap = new HashMap<>(16);
for (int i = 0; i < students.size(); i ) {
Student student = students.get(i);
Integer score = student.getScore();
List<Student> scoreList = scoreMap.get(score);
if (scoreList == null) {
scoreList = new ArrayList<>();
}
scoreList.add(student);
scoreMap.put(score, scoreList);
}
for (Map.Entry<Integer, List<Student>> entry : scoreMap.entrySet()) {
Integer score = entry.getKey();
List<Student> entryValue = entry.getValue();
System.out.println(String.format("分数为%s的学生是:", score));
System.out.println(String.format("----%s", entryValue));
}
}
java8实现
使用 Stream
进行操作
关键代码如下:
代码语言:javascript复制Map<Integer, List<Student>> scoreMap = students.stream().collect(Collectors.groupingBy(Student::getScore));
一行搞定,但是大多数情况下并非如此简单,需要在遍历过程进行其他操作,接下来引出本文的重点。
Map#computeIfAbsent
computeIfAbsent() 方法对 hashMap 中指定 key 的值进行重新计算,如果不存在这个 key,则添加到 hashMap 中。
computeIfAbsent() 方法的语法为:
代码语言:javascript复制hashmap.computeIfAbsent(K key, Function remappingFunction)
还是使用上面学生分数分组的例子进行演示,关键代码如下:
代码语言:javascript复制for (int i = 0; i < students.size(); i ) {
Student student = students.get(i);
Integer score = student.getScore();
List<Student> list = scoreMap.computeIfAbsent(score, s -> new ArrayList<>());
list.add(student);
}
对比java7
的版本:
for (int i = 0; i < students.size(); i ) {
Student student = students.get(i);
Integer score = student.getScore();
List<Student> scoreList = scoreMap.get(score);
if (scoreList == null) {
scoreList = new ArrayList<>();
}
scoreList.add(student);
scoreMap.put(score, scoreList);
}
6行代码简化成了2行!!!
到这就完了吗?
如果你使用过 google
的 guava
库的话对 Multimap
一定不陌生。
Guava工具库Multimap实现
依旧使用按照学生分数进行分组的例子进行演示,关键代码如下:
代码语言:javascript复制ListMultimap<Integer, Student> scoreMap = ArrayListMultimap.create();
for (int i = 0; i < students.size(); i ) {
Student student = students.get(i);
Integer score = student.getScore();
scoreMap.put(score, student);
}
for (Integer score : scoreMap.keySet()) {
List<Student> entryValue = scoreMap.get(score);
System.out.println(String.format("分数为%s的学生是:", score));
System.out.println(String.format("----%s", entryValue));
}
当你在构建比较复杂的 Map<K,Collection<V>>
时使用 Multimap
再合适不过了。他的应用场景是当你需要构造像 Map<K, List<V>>
或者 Map<K, Set<V>>
这样比较复杂的集合类型的数据结构,来做相应的业务逻辑处理。它会把相同key和value的值给覆盖起来,但是相同的key又可以保留不同的value。
Multimap的实现类
Multimap提供了丰富的实现,所以你可以用它来替代程序里的 Map<K, Collection<V>>
具体的实现如下:
Implementation | Keys 的行为类似 | Values的行为类似 |
---|---|---|
ArrayListMultimap | HashMap | ArrayList |
HashMultimap | HashMap | HashSet |
LinkedListMultimap | LinkedHashMap* | LinkedList* |
LinkedHashMultimap | LinkedHashMap | LinkedHashSet |
TreeMultimap | TreeMap | TreeSet |
ImmutableListMultimap | ImmutableMap | ImmutableList |
ImmutableSetMultimap | ImmutableMap | ImmutableSet |
以上这些实现,除了immutable的实现都支持null的键和值。
- LinkedListMultimap.entries()能维持迭代时的顺序。
- LinkedHashMultimap维持插入的顺序,以及键的插入顺序。
要注意并不是所有的实现都正真实现了
Map<K, Collection<V>>
(尤其是有些Multimap的实现为了最小话开销,使用了自定义的hash table)