在JDK8中Map接口提供了一些新的便利的方法。因为在本文中我所提到的所有Map方法都是以默认值方法的方式实现的,所以现有的Map接口的实现可以直接拥有这些在默认值方法中定义的默认行为,而不需要新增一行代码。本文涵盖的JDK8中引进的Map方法有:getOrDefault(Object,V),putIfAbsent(K,V),remove(Object,Object),replace(K,V),和 replace(K,V,V)。

Map范例

我将使用如下代码所示的Map声明和初始化来贯穿整篇博文中的示例。字段stateAndCapitals是类级别的静态字段。为了阅读清晰和更简单的示范一些JDK8中新的Map默认值方法,我有意的让stateAndCapitals字段只包含了美国50个洲的一个小子集。



private          final          static          Map statesAndCapitals;         


                    


          static         


          {         


                    statesAndCapitals =           new          HashMap<>();         


                    statesAndCapitals.put(          "Alaska"          ,           "Anchorage"          );         


                    statesAndCapitals.put(          "California"          ,           "Sacramento"          );         


                    statesAndCapitals.put(          "Colorado"          ,           "Denver"          );         


                    statesAndCapitals.put(          "Florida"          ,           "Tallahassee"          );         


                    statesAndCapitals.put(          "Nevada"          ,           "Las Vegas"          );         


                    statesAndCapitals.put(          "New Mexico"          ,           "Sante Fe"          );         


                    statesAndCapitals.put(          "Utah"          ,           "Salt Lake City"          );         


                    statesAndCapitals.put(          "Wyoming"          ,           "Cheyenne"          );         


          }



Map.getOrDefault(Object, V)

Map的新方法getOrDefault(Object,V)允许调用者在代码语句中规定获得在map中符合提供的键的值,否则在没有找到提供的键的匹配项的时候返回一个“默认值”。

下一段代码列举对比了如何在JDK8之前检查一个map中匹配提供键的值是否找到,没找到匹配项就使用一个默认值是如何实现的,并且现在在JDK8中是如何实现的。



/*         


                    * 示范Map.getOrDefault方法并和JDK8之前的实现方法做对比。JDK8         


                    * 中新增的Map.getOrDefault方法相比于传统的实现方法,所用的代码行数更少         


                    * 并且允许用一个final类型的变量来接收返回值。          


                    */         


                    


          // JDK8之前的实现方法         


          String capitalGeorgia = statesAndCapitals.get(          "Georgia"          );         


          if          (capitalGeorgia ==           null          )         


          {         


                    capitalGeorgia =           "Unknown"          ;         


          }         


                    


          // JDK8的实现方法         


          final          String capitalWisconsin = statesAndCapitals.getOrDefault(          "Wisconsin"          ,           "Unknown"          );


在Apache Commons包的DefaultedMap类提供了和新的Map.getOrDefault(Object, V)方法类似的功能。Groovy GDK中为Groovy包含了一个类似的方法,Map.get(Object,Object),但是这个方法的行为有一点不同,因为它不仅仅在“键”没找到的时候返回提供的默认值,而且还会将键和默认值增加到调用的map中。

Map.putIfAbsent(K,V)

Map的新方法putIfAbsent(K,V)在javadoc中已经公布了它的默认实现的等价代码:



V v = map.get(key);         


          if          (v ==           null          )         


                    v = map.put(key, value);         


                    


          return          v;



这在另一段对比JDK8之前的实现方法和JDK8的实现方法的代码示例中得到了证明。



/*         


                    * 示范Map.putIfAbsent方法并和JDK8之前的实现方法做对比。JDK8         


                    * 中新增的Map.putIfAbsent方法相比于传统的实现方法,所用的代码行数更少         


                    * 并且允许用一个final类型的变量来接收返回值。          


                    */         


                    


          // JDK8之前的实现方式         


          String capitalMississippi = statesAndCapitals.get(          "Mississippi"          );         


          if          (capitalMississippi ==           null          ){         


                    capitalMississippi = statesAndCapitals.put(          "Mississippi"          ,           "Jackson"          );         


          }         


                    


          // JDK8的实现方式         


          final          String capitalNewYork = statesAndCapitals.putIfAbsent(          "New York"          ,           "Albany"          );


在putIfAbsent方法增加之前,java方面的替代解决方案在StackOverflow上的java map.get(key)–automatically do put(key) and return if key doesn’t exist?帖子讨论过。在JDK8之前这没有任何意义,ConcurrentMap接口(继承自Map)已经提供了一个putIfabsent(K,V)方法。

Map.remove(Object.Object)

Map的新方法remove(Object,Object)超越了长期有效的Map.remove(Object)方法,只有在提供的键和值都匹配的时候才会删除该map项(之前的有效版本只是查找“键”的匹配来删除)。

该方法的javadoc的注释解释了默认值方法的具体实现在JDK8之前的java代码是如何工作的:
对于文中的map,该默认值实现是等价于新方法的:



if          (map.containsKey(key) && Objects.equals(map.get(key), value)) {         


                    map.remove(key);         


                    return          true          ;         


          }           else          {         


                    return          false          ;         


          }



下面这段代码列举展示的是新实现方法和JDK8之前的实现方法的一个具体比较。



/*         


                    * 示范Map.remove(Object,Object)方法并和JDK8之前的实现方法做对比。JDK8         


                    * 中新增的Map.remove(Object,Object)方法相比于传统的实现方法,所用的代码行数更少         


                    * 并且允许用一个final类型的变量来接收返回值。          


                    */         


                    


          // JDK8之前的实现方式         


                    boolean          removed =           false          ;         


                    if          (   statesAndCapitals.containsKey(          "New Mexico"          )         


                    && Objects.equals(statesAndCapitals.get(          "New Mexico"          ),           "Sante Fe"          )) {         


                    statesAndCapitals.remove(          "New Mexico"          ,           "Sante Fe"          );         


                    removed =           true          ;         


          }         


                    


          // JDK8的实现方式         


          final          boolean          removedJdk8 = statesAndCapitals.remove(          "California"          ,           "Sacramento"          );



Map.replace(K,V)

两个新增的Map “replace”方法中的第一个方法只有在指定的键已经存在并且有与之相关的映射值时才会将指定的键映射到指定的值(新值),javadoc的注释解释了该默认值方法的实现的等价java代码:

对于文中的map,该默认值实现是等价于新方法的:



if          (map.containsKey(key)) {         


                    return          map.put(key, value);         


          }           else          {         


                    return          null          ;         


          }



下面展示的是新方法和JDK8之前的方法比较:



/*         


                    * 示范Map.replace(K, V)方法并和JDK8之前的实现方法做对比。JDK8         


                    * 中新增的Map.replace(K, V)方法相比于传统的实现方法,所用的代码行数更少         


                    * 并且允许用一个final类型的变量来接收返回值。          


                    */         


                    


          // JDK8之前的实现方式         


          String replacedCapitalCity;         


          if          (statesAndCapitals.containsKey(          "Alaska"          ))  {         


                    replacedCapitalCity = statesAndCapitals.put(          "Alaska"          ,           "Juneau"          );         


          }         


                    


          // JDK8的实现方式         


          final          String replacedJdk8City = statesAndCapitals.replace(          "Alaska"          ,           "Juneau"          );



Map.replace(K,V,V)

第二的新增的Map replace方法在替换现存值方面有更窄的释义范围。当那个方法(上一个replace方法)只是涵盖指定的键在映射中有任意一个有效的值的替换处理,而这个“replace”方法接受一个额外的(第三个)参数,只有在指定的键和值都匹配的情况下才会替换。
javadoc注释说明了该默认值方法的实现:



if          (map.containsKey(key) && Objects.equals(map.get(key), value)) {         


                    map.put(key, newValue);         


                    return          true          ;         


          }           else          {         


                    return          false          ;         


          }



下面这段代码列举展示的是新实现方法和JDK8之前的实现方法的一个具体比较。



/*         


                    * 示范Map.replace(K, V, V)方法并和JDK8之前的实现方法做对比。JDK8         


                    * 中新增的Map.replace(K, V, V)方法相比于传统的实现方法,所用的代码行数更少         


                    * 并且允许用一个final类型的变量来接收返回值。          


                    */         


                    


          // JDK8之前的实现方式         


                    boolean          replaced =           false          ;         


                    if          (   statesAndCapitals.containsKey(          "Nevada"          )         


                    && Objects.equals(statesAndCapitals.get(          "Nevada"          ),           "Las Vegas"          )) {         


                    statesAndCapitals.put(          "Nevada"          ,           "Carson City"          );         


                    replaced =           true          ;         


                    }         


                    


          // JDK8的实现方式         


          final          boolean          replacedJdk8 = statesAndCapitals.replace(          "Nevada"          ,           "Las Vegas"          ,           "Carson City"          );



观察与结论

下面是一些根据本文得出的观察论点。

  • 对与这些JDK8中Map的新增方法,javadoc方法是很有用的,特别是在用JDK8之前的代码描述新方法的行为时。我是在JDK 8 javadoc-based API documentation的基础上更宽泛的讨论这些方法的javadoc文档。
  • 由这些方法的javadoc注释中指出的等价java代码可以得出,这些方法在访问map的键和值之前通常不会做非空检查。因此,在使用这些方法和javadoc注释中的等价java时会引发同样的空指针问题。实际上,javadoc注释通常会根据一些允许键和值为空或不为空的Map具体实现会引发空指针和其他问题的可能性而提出警告
  • 在本文中讨论的新增的Map方法都是“默认值方法”,意味着Map的具体实现会自动“继承”这些(默认值)实现
  • 在本文中讨论的新增的Map方法把代码的干净简明考虑在内。在我大多数的例子中,他们允许客户端代码从多行状态相连的语句转换成单行语句并一劳永逸的(把返回值)赋值给一个本地变量。

在本文涵盖的新增的Map方法都没有多少创造性或者重大特性更新,但是他们更便利,许多java开发者之前实现这些功能需要很多冗长的代码,为此而写他们自己的相似的方法,或者为此而使用一个第三方类库。JDK8把这些标准化的方法带给广大的java用户而不需要自定义实现或者第三方框架。因为是基于默认值方法的机制实现的,甚至那些已经存在一段时间的Map实现突然自动的就可以访问这些新增的方法而不需要做任何代码变更。