一、前言
Map的数据操作,你是不是还只会put、get?
Map是我们日常编程中十分常用的数据接口,的在JDK8中,Map引入了几个新方法,可以简化我们对Map中数据的操作。
目前JDK的最新LTS版本已经更新到21了,这几个在JDK8引入的Map”新“方法其实也是”老“方法了,还没熟练使用也太out了,快来看看你都”学废“了吗?
二、getOrDefault
这个方法名很直观,见名知意:尝试获取key对应的值,如果未获取到,就返回默认值。
看一个使用的例子,新写法会比老写法更加简洁:
package com.example.springbootdemo.test;
import java.util.HashMap;
import java.util.Map;
public class MapDemo {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("name", "admin");
map.put("age", "20");
//旧方法
if (map.containsKey("level")) {
System.out.println("旧方法:" + map.get("level"));
}
//新方法
String defaultMapData = map.getOrDefault("level", "默认值");
System.out.println("新方法:" + defaultMapData);
}
}
执行程序:
旧方法是直接判断有没有这个键,如果没有这个键就不做任何操作,也不会返回数据,但是新方法如果找不到这个键会返回默认值。
三、foreach
看方法名也可以知道,这个方法是遍历map的数据使用的。
如果没有foreach,我们遍历map的时候一般是使用增强for循环,有了这个方法后,可以更加方便使用entry中的key和val。
package com.example.springbootdemo.test;
import java.util.HashMap;
import java.util.Map;
public class MapDemo {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("name", "admin");
map.put("age", "20");
//旧方法
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("key=>" + entry.getKey() + ", value=>" + entry.getValue());
}
System.out.println("----------------------");
//新方法
map.forEach((k, v) -> System.out.println("key=>" + k + ", value=>" + v));
}
}
执行程序:
四、merge
名字可以想到,是合并entry使用的,但是具体是怎么合并呢?
看一下日常最常用的Map实现类HashMap对merge方法的实现
@Override
public V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
if (value == null || remappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
Node<K,V>[] tab; Node<K,V> first; int n, i;
int binCount = 0;
TreeNode<K,V> t = null;
Node<K,V> old = null;
if (size > threshold || (tab = table) == null ||
(n = tab.length) == 0)
n = (tab = resize()).length;
if ((first = tab[i = (n - 1) & hash]) != null) {
if (first instanceof TreeNode)
old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
else {
Node<K,V> e = first; K k;
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
old = e;
break;
}
++binCount;
} while ((e = e.next) != null);
}
}
if (old != null) {
V v;
if (old.value != null) {
int mc = modCount;
v = remappingFunction.apply(old.value, value);
if (mc != modCount) {
throw new ConcurrentModificationException();
}
} else {
v = value;
}
if (v != null) {
old.value = v;
afterNodeAccess(old);
}
else
removeNode(hash, key, null, false, true);
return v;
} else {
if (t != null)
t.putTreeVal(this, tab, hash, key, value);
else {
tab[i] = newNode(hash, key, value, first);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
}
++modCount;
++size;
afterNodeInsertion(true);
return value;
}
}
代码比较长,但是实现的效果比较容易描述:这个方法接收3个参数:key、value、function。
- 如果key存在,将value按照function做1次计算后,更新到Map中
- 如果key不存在,将key-value放入Map中
这个方法在某些场景中挺好用的,代码简洁易懂,例如:我们有1个List,要统计List中每个元素出现的次数。我们要实现的逻辑是,遍历List中的每个元素,如果这个元素在Map中存在,Map中的值+1;如果不存在,则放入Map中,次数(值)为1。
package com.example.springbootdemo.test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MapDemo {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
List<String> list = Arrays.asList("apple", "orange", "banana", "orange");
//旧方法
for (String item : list) {
if (map.containsKey(item)) {
map.put(item, map.get(item) + 1);
} else {
map.put(item, 1);
}
}
System.out.println("----------------------");
//新方法
for (String item : list) {
map.merge(item, 1, Integer::sum);
}
}
}
可以看到我们使用merge方法的话,只用1行就简洁实现了这个逻辑。
五、putIfAbsent
也是一个见名知意的方法:不存在key或者值为null时,才将键值对放入Map。跟put方法相比,这个方法不会直接覆盖已有的值,在不允许覆盖旧值的场景使用起来会比较简洁。
package com.example.springbootdemo.test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MapDemo {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("zhangsan", 1);
map.put("lisi", 2);
//旧方法
if (!map.containsKey("lisi")) {
map.put("lisi", 3);
}
System.out.println("----------------------");
//新方法
map.putIfAbsent("lisi", 1);
}
}
执行程序:
六、computer
omputer方法需要传入2个参数:key、function。主要有3步操作
- 获取到key对应的oldValue,可能为null
- 经过function计算获取newValue
- put(key, newValue)
还是以刚刚统计单次次数需求为例,看一下computer的写法:
private static void testComputer() {
Map<String, Integer> cntMap = new HashMap<>(8);
List<String> list = Arrays.asList("apple", "orange", "banana", "orange");
// 老写法
for (String item : list) {
if (cntMap.containsKey(item)) {
cntMap.put(item, cntMap.get(item) + 1);
} else {
cntMap.put(item, 1);
}
}
// 新写法
for (String item : list) {
cntMap.compute(item, (k, v) -> {
if (v == null) {
v = 1;
} else {
v += 1;
}
return v;
});
}
}
七、computeIfAbsent
看名字就知道是compute方法衍生出来的方法,这个方法只在key不存在的时候,执行computer计算,如果说key对应的value存在,就直接返回这个value。
例如,我们需要计算斐波那锲数列的时候,可以使用这个方法来简化代码:
private static void testComputerIfAbsent() {
Map<Integer, Integer> fabMap = new ConcurrentHashMap<>(16);
fabMap.put(0, 1);
fabMap.put(1, 1);
System.out.println(fab(5, fabMap));
}
private static Integer fab(Integer index, Map<Integer, Integer> fabMap) {
return fabMap.computeIfAbsent(index, i -> fab(i - 2, fabMap) + fab(i - 1, fabMap));
}
八、computeIfPresent
这个是computeIfAbsent的姊妹方法,区别在于,这个方法是只有key存在的时候,才去执行computer计算和值的更新。
九、replace
这个方法的效果是:
- 如果key存在,则更新值
- 如果key不存在,什么也不做
package com.example.springbootdemo.test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MapDemo {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("zhangsan", 1);
map.put("lisi", 2);
//旧方法
if (map.containsKey("lisi")) {
map.put("lisi", 3);
}
System.out.println(map);
System.out.println("----------------------");
//新方法
map.replace("lisi", 4);
System.out.println(map);
}
}
执行程序:
十、总结
可以看到,这些JDK8引入的Map的方法,都可以在某些特定场景下简化我们的代码,虽然不嫌麻烦的话,put、get等方法都可以搞定,让我想起一张远古的图
不过在不同的场景使用不同的方法,尽量把代码写的简洁和优雅,才是一个程序猿不断追求的目标吧。