首页 > 代码库 > 译文:Java8 Map Enhancement

译文:Java8 Map Enhancement

  此篇为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) pair14 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=1B=2C=3

merge() method

这个方法有点不好理解。Javadoc 用到了这样一个例子:

map.merge(key, msg, String::concat)

 

考虑到下面这条规约:

如果原来该key不存在或者对应value为空的话,把本次计算的结果插入到map中。否则的话用新值替换掉旧值,如果新值为null就将该null值插如集合中。

因此上面的语句可以翻译为下面一系列的原子操作:

String value =http://www.mamicode.com/ map.get(key);if (value =http://www.mamicode.com/= 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()来插入。
tweet this

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值而变得复杂。tweet this

实用建议

还有更多的方法, 像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/