此篇为Jooq官方博客的一篇译文,特此声明。
Java 8 的好处: Map Enhancements
此次增强的 大部分API实际上是 新的 Streams API的一部分。但是一些新的特性同样加入到 java.util.List
之中 并且最重要的是对 java.util.Map 的增强。
为了保证向后兼容, 所有被加入到Interface中的方法皆为默认方法。因此我们在使用过程中会有一些意外的小惊喜。
compute() methods
过去,我们常获取一个map集合的view,在view上对其做一些修改、计算然后将其重新插入到map中。如果牵扯到并发编程的话这个过程是啰嗦和困难的。但是在Java8中,我们可以把一个 BiFunction
传递给新(加入的) compute()、
computeIfAbsent()、或者
computeIfPresent()
方法中 然后让Map实现值替换的语义。下面的例子展示了这一具体过程:
1 // We‘ll be using this simple map 2 // Unfortunately, still no map literals in Java 8.. 3 Map<String, Integer> map = new HashMap<>(); 4 map.put("A", 1); 5 map.put("B", 2); 6 map.put("C", 3); 7 8 // Compute a new value for the existing key 9 System.out.println(map.compute("A", 10 (k, v) -> v == null ? 42 : v + 41)); 11 System.out.println(map); 12 13 // This will add a new (key, value) pair 14 System.out.println(map.compute("X", 15 (k, v) -> v == null ? 42 : v + 41)); 16 System.out.println(map);
上面代码的输出为:
42 {A=42, B=2, C=3} 42 {A=42, B=2, C=3, X=42}
这些新功能对ConcurrentHashMap来说是非常有用的,
考虑到其如下的保证:
整个的方法调用时自动完成的。当本线程更新这个map时其他更新线程将会被阻塞,因此计算应该是短而简洁的,同时必须禁止更新这个map的其他映射关系。
forEach() method
这真的是一个非常好的增强,它让你传递一个方法引用或者Lambda表达式去 一个一个地获取(key, value) 对. 下面是一个尝试性的例子:
map.forEach((k, v) ->
System.out.println(k + "=" + v));
输出:
A=1 B=2 C=3
merge() method
这个方法有点不好理解。Javadoc 用到了这样一个例子:
map.merge(key, msg, String::concat)
考虑到下面这条规约:
如果原来该key不存在或者对应value为空的话,把本次计算的结果插入到map中。否则的话用新值替换掉旧值,如果新值为null就将该null值插如集合中。
因此上面的语句可以翻译为下面一系列的原子操作:
String value = map.get(key); if (value == null) map.put(key, msg); else map.put(key, value.concat(msg));
这当然不是一个常用功能,没有作为Top API. 另外,如果map中已经包含 null
值(null
值是被允许的),同时你的remappingFunction
返回 null,
那么这条记录将会被删除。这个是很值得意外的。 来看下面一段程序:
map.put("X", null); System.out.println(map.merge( "X", null, (v1, v2) -> null)); System.out.println(map);
其输出为:
null {A=1, B=2, C=3}
更新:我第一次写这个程序用的是 JDK 8 build 116版本。使用 build 129版本的时候情况已经完全不同了。首先,传给merge()的值不允许为
null。其次
null值
被 merge()
视为缺少值。下面的代码产生同样的效果:
map.put("X", 1); System.out.println(map.merge( "X", 1, (v1, v2) -> null)); System.out.println(map);
该 merge()
操作从map里删除了值。这可能是OK的,因为如果用SQL的方式来理解,“merge” 的语义通常就是 INSERT,
UPDATE,和
DELETE
的融合。
但是map是被允许包含 null 值的,
但是不能通过 merge()来插入。
getOrDefault()
这个函数是无脑的。对吗?对吗?大错特错!
不幸的是,有两种Maps。一种支持 null
键 和/或 值, 一种不支持 nulls。
先前的 merge()
并没有对这两种map进行区分, 当键不存在时,这个全新的 getOrDefault()
仅仅返回默认值。它没有防止 NullPointerException
:
map.put("X", null); try { System.out.println(map.getOrDefault("X", 21) + 21); } catch (NullPointerException nope) { nope.printStackTrace(); }
这是懒汉行为 。总的来说,Map API因为null值而变得复杂。
实用建议
还有更多的方法, 像putIfAbsent()
(从ConcurrentHashMap中抽离出来的),
remove()
(带键值参数),replace()。
结论
总的来说,可以说许多原子操作已经成为*的 Map API,这是好事。但同时关于null值得语义模糊加深了。 “present” vs. “absent”, “contains”, “default” 这些术语没有对理解提供帮助, 违反了 API consistent and most importantly, regular. 因此使用新API时键值最好都是非null值!
更多关于Java 8
同时看下这里 Eugen Paraschiv’s awesome Java 8 resources page
本文翻译自:http://blog.jooq.org/2014/02/14/java-8-friday-goodies-map-enhancements/