如果说什么是 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);
}