1.引言
最近一直在忙于更改之前的代码,由于项目时间线比较久,存在好多的API使用级别的优化。现在将最近在HashMap使用上的优化分享给大家。
本文将着重展示map接口的一下几个API的使用,相信要是用的好的话,可以极大的简化我们的代码。
1. putIfAbsent: 如果map不包含key,放入map, 返回旧值(null)
2. computeIfAbsent: 如果map不包含key, 初始化key所对应的value,放入map中,返回新值。方法入参 key, 返回值缺失时需要初始化的值。
3. computeIfPresent: 如果map中包含key, 执行自定义的函数操作,否则返回null。方法入参当前操作的key和key对应的旧值(不会是null),返回值根据一定规则merge的值。
4.compute: 相当于2,3 的通用版,将控制权交给用户了,用户可以自定义自己的逻辑。方法入参当前操作的key和key对应的旧值(可能是null),返回值根据一定规则merge的值。
下面我会通过一些例子来加深大家对这几个API的理解。
2.发现重复key时不放入
> 老代码
Map<String, String> map = new HashMap<>();
if(!map.containsKey("key")) map.put("key", "val");
> 修正版
Map<String, String> map = new HashMap<>();
map.putIfAbsent("key", "val");
3. 数据分组存入Map
> 老代码
Map<String, List<Employee>> map = new HashMap<>();
for(Employee employee: employees) {
if(!map.containsKey(employee.getName())) {
List<Employee> list = new ArrayList<>();
list.add(employee);
map.put(employee.getName(), list);
}else {
map.get(employee.getName()).add(employee);
}
}
> 修正版
1) putIfAbsent版本
Map<String, List<Employee>> map = new HashMap<>();
employees.forEach(employee -> {
// putIfAbsent 返回旧值(可能是null), 无法实现in-line调用
map.putIfAbsent(employee.getName(), new ArrayList<>());
map.get(employee.getName()).add(employee);
});
2) computeIfAbsent版本
Map<String, List<Employee>> map = new HashMap<>();
// 利用computeIfAbsent返回新值特性,实现in-line方法调用
employees.forEach(employee -> map.computeIfAbsent(employee.getName(), key -> new ArrayList<>()).add(employee));
3) computeIfPresent版本
Map<String, List<Employee>> map = new HashMap<>();
employees.forEach(employee -> {
// 合并重复key
List<Employee> list = map.computeIfPresent(employee.getName(), (key, value) -> {
value.add(employee);
return value;
});
// 第一次放{key, value}
if (Objects.isNull(list)) {
map.put(employee.getName(), Stream.of(employee).collect(Collectors.toList()));
}
});
4) compute版本
Map<String, List<Employee>> map = new HashMap<>();
employees.forEach(employee ->
// 处理value初始化, 利用compute返回新值特性,实现in-line方法调用
map.compute(employee.getName(), (key, value) -> Objects.isNull(value) ? new ArrayList<>() : value)
// 添加分组内的元素
.add(employee));
综上,computeIfAbsent和compute在代码风格上更简洁,而computeIfAbsent更加适配上面的情形。编程就应该朝着简洁高效的方向进行,这是所有编程语言共通的。
4. 数据分组汇总(求和)
>老代码
Map<String, Double> map = new HashMap<>();
for (Employee employee : employees) {
if (!map.containsKey(employee.getName())) {
map.put(employee.getName(), employee.getSalary());
} else {
double merged = map.get(employee.name) + employee.getSalary();
map.put(employee.getName(), merged);
}
}
>修正版
1) putIfAbsent 版本
Map<String, Double> map = new HashMap<>();
employees.forEach(employee -> {
// 注意这里使用包装类型
Double previous = map.putIfAbsent(employee.getName(), employee.getSalary());
// 借助 putIfAbsent 返回旧值,追溯当前key的前一次累加值
if (Objects.nonNull(previous)) map.put(employee.getName(), previous + employee.getSalary());
});
2) computeIfAbsent 版本
Map<String, Double> map = new HashMap<>();
employees.forEach(employee -> {
// computeIfAbsent 每次返回新值,需要处理第一次和不是第一次的情况,否则就造成数据丢失
double previous = map.computeIfAbsent(employee.getName(), key -> map.containsKey(key) ? map.get(key) : 0.0);
map.put(employee.getName(), previous + employee.getSalary());
});
3) computeIfPresent 版本
Map<String, Double> map = new HashMap<>();
employees.forEach(employee -> {
// computeIfPresent 完成数据汇总
map.computeIfPresent(employee.getName(), (key, value) -> value + employee.getSalary());
// putIfAbsent 完成第一次key值放入
map.putIfAbsent(employee.getName(), employee.getSalary());
});
4) compute 版本
Map<String, Double> map = new HashMap<>();
employees.forEach(
// 利用compute 自己定义merge规则,实现方法in-line调用
employee -> map.compute(employee.getName(),
(key, value) -> Objects.isNull(value) ? employee.getSalary() : value + employee.getSalary()
)
);
综上,在根据key合并value时,compute提供了很好的优化能够达到in-line调用。
5.结尾
本篇文章主要是就Java8提供的新的Map接口做了一些demo, 相信大家如果能灵活应用这些API,一定可以简化map数据的构造过程。不过任何事情都不是一蹴而就的,还是需要在实际开发中多多使用才能达到熟能生巧,融会贯通,极大简化Java代码(Java 过于繁复的语言结构一直为人所诟病,这也算是一次小小的优化了)。
最后各位如果感觉这篇文章对你们有帮助的话,请动一下你们的右手食指,点个赞哈。本人在此感谢各位看官了。