如果说什么是 Java 集合中的性能天花板,那 Map 肯定是当仁不让,但如果又问什么每个 Crud Boy 的终极噩梦,那空指针 (NPE) 必然将榜上有名。
HashMap 作为 JDK 1.2 版本就被引入的结果在设计之初并没有针对空数据有着特殊的限制,一不留神就可能踩坑从而引发连锁反应。
因此,随着 JDK 的不断更新迭代,在 JDK 8 中引入 compute() 及 merge() 等一系列新方法进一步提高代码健壮性,今天就让我们来一探究竟。
1. compute()
compute() 是最基础也是最通用的方法,顾名思义即计算,可以针对指定 Key 进行重新计算赋值,作用相当于 get() + put() 结合体。
其第二个方法入参 BiFunction 输入两个值分表表示当前 Key 与 Value,最后返回的结果即新值。下述示例即将对应元素的值替换为新旧值字符串拼接后结果。
public void demo() {
Map<String, String> map = new HashMap<>();
map.put("a", "a1");
// 计算赋值
map.compute("a", (k, v) -> {
String interval = "^";
return String.format("%s%s%s", v, interval, "a2");
});
// {a=a1^a2}
System.out.println(map);
}
当正像上述提到那样,其类似于 get() 与 put() 结合体,因此若 Key 不存在或对应值为空时上述的 (k, v) 中的 v 同样是为空,若无处理将会抛出 NPE 异常。
同样有一个相对容易让人忽略的事项,当 compute() 计算返回 null 时,其并非存入一个 (key, null) 的节点,而是将对应 key 从集合中删除,在通过 containsKey() 判断时将返回 false。
即与 JDK 8 之后许多新引入的特性秉持着同一理念,尽量避免 key 或 value 为空,当通过 get() 方式获取结果为空时理应代表着 key 不存在避免二义性。
public void demo() {
Map<String, String> map = new HashMap<>();
map.put("a", "a1");
// 空则删除元素
map.compute("a", (k, v) -> null);
// 返回 false
System.out.println(map.containsKey("a"));
}
2. computeIfAbsent()
从名字即可看出,computeIfAbsent() 是 compute() 的特例,即当 Key 不存在时执行,若存在则不会触发。
与 compute() 不同的其触发时 Key 肯定是不存在的,因此第二个参数输入为 Function,即仅支持输入一个参数代表 Key。
public void demo() {
Map<String, String> map = new HashMap<>();
// a 不存在,写入
map.computeIfAbsent("a", k -> String.format("%s^%s", k, "v1"));
// a 存在,不执行
map.computeIfAbsent("a", k -> String.format("%s^%s", k, "v2"));
// {a=a^v1}
System.out.println(map);
}
3. computeIfPresent()
computeIfPresent() 同样为 compute() 的一种特例,作用则刚好与 computeIfAbsent() 相反,即只在 Key 存在的时候执行计算并覆盖原值,这里就不再展示示例介绍。
4. merge()
故名思意 merge() 即用于合并,即合并对应 Key 的新旧值后放回容器,旧值不存在则用新值替换,返回 null 时同样删除该元素节点。
其与 compute() 既有相当又有不同,最直观的表现即方法入参,merge() 方法接收 3 个参数:(k, v, (o,n)),分别代表着 Key,新的 Value 以及新旧值函数参数。
例如下述示例即拼接对应 key=a 的元素节点:
public void demo() {
Map<String, String> map = new HashMap<>();
map.put("a", "v1");
map.merge("a", "v2", (o, n) -> {
String interval = "^";
return String.format("%s%s%s", o, interval, n);
});
// {a=v1^v2}
System.out.println(map);
}
看到这你或许会有疑惑,merge() 与 compute() 有和区别?能用 merge() 实现的通过 compute() 同样能够实现。
事实也的确如此,可以将 merge() 理解为 compute() 的一种特例,compute() 表示针对任意类型计算操作,而 merge() 则更倾向于针对数据的合并操作,同样其自带了部分数据预处理。
观察 HashMap 的 merge() 方法实现可以看到,除在元素不存在即 old = null 时直接替换,在元素存在时但旧值为空时仍会执行替换,如此一来即为我们省去空判断处理。
通过下面这个示例,就可以直观的看出二者所带来的代码差异,显然合并计算相关操作 merge() 实现简洁性更高。
public void demo1() {
Map<String, List<String>> map = new HashMap<>();
map.put("a", null);
map.compute("a", (k, v) -> {
if (v == null) {
v = new ArrayList<>();
}
v.add("a1");
return v;
});
System.out.println(map);
}
public void demo2() {
Map<String, List<String>> map = new HashMap<>();
map.put("a", null);
List<String> v1 = new ArrayList<>(List.of("a1"));
map.merge(key, v1, (o, n) -> {
o.addAll(n);
return o;
});
System.out.println(map);
}