Map 只會(huì) put、get?快來(lái)學(xué)這幾個(gè)“新”方法
引子
Map的數(shù)據(jù)操作,你是不是還只會(huì)put、get?
Map是我們?nèi)粘>幊讨惺殖S玫臄?shù)據(jù)接口,的在JDK8中,Map引入了幾個(gè)新方法,可以簡(jiǎn)化我們對(duì)Map中數(shù)據(jù)的操作。
目前JDK的最新LTS版本已經(jīng)更新到21了,這幾個(gè)在JDK8引入的Map”新“方法其實(shí)也是”老“方法了,還沒(méi)熟練使用也太out了,快來(lái)看看你都”學(xué)廢“了嗎?
getOrDefault
這個(gè)方法名很直觀(guān),見(jiàn)名知意:嘗試獲取key對(duì)應(yīng)的值,如果未獲取到,就返回默認(rèn)值。
看一個(gè)使用的例子,新寫(xiě)法會(huì)比老寫(xiě)法更加簡(jiǎn)潔:
private static void testGetOrDefault() {
Map<String, String> map = new HashMap<>(4);
map.put('123', '123');
String key = 'key';
String defaultValue = 'defaultValue';
// 老寫(xiě)法
String oldValue = defaultValue;
if (map.containsKey(key)) {
oldValue = map.get(key);
}
System.out.println('oldValue = ' + oldValue);
// 新寫(xiě)法
String newValue = map.getOrDefault(key, defaultValue);
System.out.println('newValue = ' + newValue);
}
foreach
看方法名也可以知道,這個(gè)方法是遍歷map的數(shù)據(jù)使用的。
如果沒(méi)有foreach,我們遍歷map的時(shí)候一般是使用增強(qiáng)for循環(huán),有了這個(gè)方法后,可以更加方便使用entry中的key和val:
private static void testForeach() {
Map<String, String> map = new HashMap<>(4);
map.put('123', '123');
// 老寫(xiě)法
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.printf('老寫(xiě)法 key = %s, value = %s%n', entry.getKey(), entry.getValue());
}
// 新寫(xiě)法
map.forEach((key, value) -> System.out.printf('新寫(xiě)法 key = %s, value = %s%n', key, value));
}
merge
從名字可以想到,是合并entry使用的,但是具體是怎么合并呢?
看一下日常最常用的Map實(shí)現(xiàn)類(lèi)HashMap對(duì)merge方法的實(shí)現(xiàn)
@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;
}
}
代碼比較長(zhǎng),但是實(shí)現(xiàn)的效果比較容易描述:這個(gè)方法接收3個(gè)參數(shù):key、value、function。
- 如果key存在,將value按照f(shuō)unction做1次計(jì)算后,更新到Map中
- 如果key不存在,將key-value放入Map中
這個(gè)方法在某些場(chǎng)景中挺好用的,代碼簡(jiǎn)潔易懂,例如:我們有1個(gè)List,要統(tǒng)計(jì)List中每個(gè)元素出現(xiàn)的次數(shù)。我們要實(shí)現(xiàn)的邏輯是,遍歷List中的每個(gè)元素,如果這個(gè)元素在Map中存在,Map中的值+1;如果不存在,則放入Map中,次數(shù)(值)為1。
private static void testMerge() {
Map<String, Integer> cntMap = new HashMap<>(8);
List<String> list = Arrays.asList('apple', 'orange', 'banana', 'orange');
// 老寫(xiě)法
for (String item : list) {
if (cntMap.containsKey(item)) {
cntMap.put(item, cntMap.get(item) + 1);
} else {
cntMap.put(item, 1);
}
}
// 新寫(xiě)法
for (String item : list) {
cntMap.merge(item, 1, Integer::sum);
}
}
可以看到我們使用merge方法的話(huà),只用1行就簡(jiǎn)潔實(shí)現(xiàn)了這個(gè)邏輯。
putIfAbsent
也是一個(gè)見(jiàn)名知意的方法:不存在key或者值為null時(shí),才將鍵值對(duì)放入Map。跟put方法相比,這個(gè)方法不會(huì)直接覆蓋已有的值,在不允許覆蓋舊值的場(chǎng)景使用起來(lái)會(huì)比較簡(jiǎn)潔。
private static void testPutIfAbsent() {
Map<String, Integer> scoreMap = new HashMap<>(4);
scoreMap.put('Jim', 88);
scoreMap.put('Lily', 90);
// 老寫(xiě)法
if (!scoreMap.containsKey('Lily')) {
scoreMap.put('Lily', 98);
}
// 新寫(xiě)法
scoreMap.putIfAbsent('Lily', 98);
}
computer
computer方法需要傳入2個(gè)參數(shù):key、function。主要有3步操作
- 獲取到key對(duì)應(yīng)的oldValue,可能為null
- 經(jīng)過(guò)function計(jì)算獲取newValue
- put(key, newValue)
還是以剛剛統(tǒng)計(jì)單次次數(shù)需求為例,看一下computer的寫(xiě)法:
private static void testComputer() {
Map<String, Integer> cntMap = new HashMap<>(8);
List<String> list = Arrays.asList('apple', 'orange', 'banana', 'orange');
// 老寫(xiě)法
for (String item : list) {
if (cntMap.containsKey(item)) {
cntMap.put(item, cntMap.get(item) + 1);
} else {
cntMap.put(item, 1);
}
}
// 新寫(xiě)法
for (String item : list) {
cntMap.compute(item, (k, v) -> {
if (v == null) {
v = 1;
} else {
v += 1;
}
return v;
});
}
}
computeIfAbsent
看名字就知道是compute方法衍生出來(lái)的方法,這個(gè)方法只在key不存在的時(shí)候,執(zhí)行computer計(jì)算,如果說(shuō)key對(duì)應(yīng)的value存在,就直接返回這個(gè)value。
例如,我們需要計(jì)算斐波那鍥數(shù)列的時(shí)候,可以使用這個(gè)方法來(lái)簡(jiǎn)化代碼:
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
這個(gè)是computeIfAbsent的姊妹方法,區(qū)別在于,這個(gè)方法是只有key存在的時(shí)候,才去執(zhí)行computer計(jì)算和值的更新。
replace
這個(gè)方法的效果是:
- 如果key存在,則更新值
- 如果key不存在,什么也不做