Map接口在1

前言

Map接口在1.8版本新增以下几个有趣的方法,今天参考源码来学习一下.

  • getOrDefault
  • replaceAll
  • putIfAbsent
  • remove
  • replace
  • computeIfAbsent
  • computeIfPresent
  • compute
  • merge

V getOrDefault(Object key, V defaultValue)

这可以说是最常用的方法了吧,获取指定key的value,当key不存在的时候返回一个默认值,也就是第二个参数.

1
2
3
4
5
6
7

default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}

void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)

将所有value替换成给定lambda的计算结果,lambda的作用为根据key和value算出新的value.

源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}

// ise thrown from function is not a cme.
v = function.apply(k, v);

try {
entry.setValue(v);
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
}
}

示例代码如下:

1
2
3
4
5
6
7
8
9
10
@Test
public void test1() {
Map<Integer, Integer> test = new HashMap<>();
test.put(1, 1);
test.put(2, 2);
System.out.println(test.toString());

test.replaceAll((k, v) -> k + v);
System.out.println(test.toString());
}

这段代码中传递了一个lambda,作用是将key和value相加作为新的value.

输出结果如下:

1
2
3
{1=1, 2=2}
{1=2, 2=4}

V putIfAbsent(K key, V value)

当key不存在的时候,写入新值.始终返回执行操作后的新值.

源代码如下:

1
2
3
4
5
6
7
8
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}

return v;
}

测试代码及输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

@Test
public void test2() {
Map<Integer, Integer> test = new HashMap<>();
test.put(1, 1);
test.put(2, 2);
System.out.println(test.toString());

test.putIfAbsent(1, 3);
test.putIfAbsent(3, 3);
System.out.println(test.toString());
}
------------------------
{1=1, 2=2}
{1=1, 2=2, 3=3}

boolean remove(Object key, Object value)

如果给定的key在map中的value与给定值相等,则移除并且返回true,否则返回false.

1
2
3
4
5
6
7
8
9
default boolean remove(Object key, Object value) {
Object curValue = get(key);
if (!Objects.equals(curValue, value) ||
(curValue == null && !containsKey(key))) {
return false;
}
remove(key);
return true;
}

replace

这个有两个重载方法.

default boolean replace(K key, V oldValue, V newValue)

当key在map中的value与给定的oldValue相等,则用newValue替换掉并且返回true,否则返回false.

1
2
3
4
5
6
7
8
9
10
11

default boolean replace(K key, V oldValue, V newValue) {
Object curValue = get(key);
if (!Objects.equals(curValue, oldValue) ||
(curValue == null && !containsKey(key))) {
return false;
}
put(key, newValue);
return true;
}

V replace(K key, V value)

当key存在,就替换掉并且返回新值,否则返回null.

1
2
3
4
5
6
7
8
default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}

V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)

如果key不存在,则使用lambda计算并写入新值.永远返回执行操作后的新值.(可以存在,不做任何操作);放回计算的新值.

这个方法可以为一些耗时或者耗资源的操作构建本地缓存,当元素存在时直接返回,当不存在的时候进行耗时进行并存储,下一次可以直接返回.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}

return v;
}

测试代码及输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    @Test
public void test3() {
Map<Integer, Integer> test = new HashMap<>();
test.put(1, 1);
test.put(2, 2);
System.out.println(test.toString());

// 1 存在,不做任何操作
test.computeIfAbsent(1, key -> key + 2);
// 3 不存在,将3 +2 = 5.
test.computeIfAbsent(3, key -> key + 2);
System.out.println(test.toString());
}

------------------------
{1=1, 2=2}
{1=1, 2=2, 3=5}

V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

当key存在时,计算新值,如果新值不为空,则将新值写入,如果新值为空,则移除掉此key.返回新值或者null.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
default V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue;
if ((oldValue = get(key)) != null) {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null) {
put(key, newValue);
return newValue;
} else {
remove(key);
return null;
}
} else {
return null;
}
}

测试代码及输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void test4() {
Map<Integer, Integer> test = new HashMap<>();
test.put(1, 1);
test.put(2, 2);
System.out.println(test.toString());

// 1 存在,计算
test.computeIfPresent(1, (key, oldValue) -> key + oldValue + 2);
// 3 不存在,不作操作
test.computeIfPresent(3, (key, oldValue) -> key + oldValue + 2);
System.out.println(test.toString());
}
-------------------------------
{1=1, 2=2}
{1=4, 2=2}

这个方法基本上是上一个方法的存在版本,但是要注意传入的lambda,参数是两个,computeIfAbsent的lambda传入key,计算值.
而computeIfPresent传入key和旧的value,并且由他们两个计算得到新的值.

V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

直接计算新值,新值为空,则删除key并且返回null,新值不为空则写入并且返回新值.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
default V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue = get(key);

V newValue = remappingFunction.apply(key, oldValue);
if (newValue == null) {
// delete mapping
if (oldValue != null || containsKey(key)) {
// something to remove
remove(key);
return null;
} else {
// nothing to do. Leave things as they were.
return null;
}
} else {
// add or replace old mapping
put(key, newValue);
return newValue;
}
}

测试代码及输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 @Test
public void test5() {
Map<Integer, Integer> test = new HashMap<>();
test.put(1, 1);
test.put(2, 2);
System.out.println(test.toString());

test.compute(1, (key, oldValue) -> key + 2);
test.compute(3, (key, oldValue) -> key + 2);
test.compute(2, (key, oldValue) -> null);
System.out.println(test.toString());
}
----------------
{1=1, 2=2}
{1=3, 3=5}

测试代码中对2进行compute->null,结果是2被删除.

V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)

使用旧值和给定的value来计算新值,如果新值为空,则删除key,不为空则写入并且返回.

注意:如果旧值为空,也就是原有的key不存在,新值等于给定值,不会再进行计算.因此下方的测试代码3=10而不是12.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}

测试代码及输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    @Test
public void test6() {
Map<Integer, Integer> test = new HashMap<>();
test.put(1, 1);
test.put(2, 2);
System.out.println(test.toString());

test.merge(1, 10, (v, oldV) -> v + oldV + 2);
test.merge(3, 10, (v, oldV) -> v + oldV + 2);
test.merge(2, 10, (v, oldV) -> null);
System.out.println(test.toString());
}
--------------------------
{1=1, 2=2}
{1=13, 3=10}

总结

其实看过源码就可以发现,除了Function,BiFunction等函数式接口(也就是用于lambda)的接口是新声明的,其他调用的API都是原先已有的put,get,contain等常用API,因此这些新的方法并不能算是很难用的新功能,只能算是一些免去开发人员重复工作的语法糖,我们当然要多多享受语法糖带来的便利,但是不能忘却原理,要多多熟悉再使用.


完。



ChangeLog

2019-05-13 完成

以上皆为个人所思所得,如有错误欢迎评论区指正。

欢迎转载,烦请署名并保留原文链接。

联系邮箱:huyanshi2580@gmail.com

更多学习笔记见个人博客——>呼延十