JDK8新增便利的Map默认值⽅法
在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中符合提供的键的值,否则在没有到提供的键的匹配项的时候返回⼀个“默认值”。
下⼀段代码列举对⽐了如何在JDK8之前检查⼀个map中匹配提供键的值是否到,没到匹配项就使⽤⼀个默认值是如何实现的,并且现在在JDK8中是如何实现的。
/*
* ⽰范OrDefault⽅法并和JDK8之前的实现⽅法做对⽐。JDK8
* 中新增的OrDefault⽅法相⽐于传统的实现⽅法,所⽤的代码⾏数更少
* 并且允许⽤⼀个final类型的变量来接收返回值。
*/
// JDK8之前的实现⽅法
String capitalGeorgia = ("Georgia");
if (capitalGeorgia == null)
{
capitalGeorgia = "Unknown";
}
// JDK8的实现⽅法
final String capitalWisconsin = OrDefault("Wisconsin", "Unknown");
在Apache Commons包的DefaultedMap类提供了和新的OrDefault(Object, V)⽅法类似的功能。Groovy GDK中为Groovy包含了⼀个类似的⽅法,(Object,Object),但是这个⽅法的⾏为有⼀点不同,因为它不仅仅在“键”没到的时候返回提供的默认值,⽽且还会将键和默认值增加到调⽤的map中。
Map.putIfAbsent(K,V)
Map的新⽅法putIfAbsent(K,V)在javadoc中已经公布了它的默认实现的等价代码:
V v = (key);
if (v == null)
v = map.put(key, value);
return v;
这在另⼀段对⽐JDK8之前的实现⽅法和JDK8的实现⽅法的代码⽰例中得到了证明。
/*
* ⽰范Map.putIfAbsent⽅法并和JDK8之前的实现⽅法做对⽐。JDK8
* 中新增的Map.putIfAbsent⽅法相⽐于传统的实现⽅法,所⽤的代码⾏数更少
* 并且允许⽤⼀个final类型的变量来接收返回值。
*/
// JDK8之前的实现⽅式
String capitalMississippi = ("Mississippi");
if (capitalMississippi == null){
capitalMississippi = statesAndCapitals.put("Mississippi", "Jackson");
}
// JDK8的实现⽅式
java replace方法
final String capitalNewYork = statesAndCapitals.putIfAbsent("New York", "Albany");
在putIfAbsent⽅法增加之前,java⽅⾯的替代解决⽅案在StackOverflow上的(key)–automatically do put(key) and return if key doesn’t exist?帖⼦讨论过。在JDK8之前这没有任何意义,ConcurrentMap接⼝(继承⾃Map)已经提供了⼀个putIfabsent(K,V)⽅法。
Map的新⽅法remove(Object,Object)超越了长期有效的ve(Object)⽅法,只有在提供的键和值都匹配的时候才会删除该map 项(之前的有效版本只是查“键”的匹配来删除)。
该⽅法的javadoc的注释解释了默认值⽅法的具体实现在JDK8之前的java代码是如何⼯作的:
对于⽂中的map,该默认值实现是等价于新⽅法的:
if (ainsKey(key) && Objects.(key), value)) {
return true;
} else {
return false;
}
下⾯这段代码列举展⽰的是新实现⽅法和JDK8之前的实现⽅法的⼀个具体⽐较。
/*
* ⽰范ve(Object,Object)⽅法并和JDK8之前的实现⽅法做对⽐。JDK8
* 中新增的ve(Object,Object)⽅法相⽐于传统的实现⽅法,所⽤的代码⾏数更少
* 并且允许⽤⼀个final类型的变量来接收返回值。
*/
/
/ JDK8之前的实现⽅式
boolean removed = false;
if (  ainsKey("New Mexico")
&& Objects.("New Mexico"), "Sante Fe")) {
removed = true;
}
// JDK8的实现⽅式
final boolean removedJdk8 = ve("California", "Sacramento");
两个新增的Map “replace”⽅法中的第⼀个⽅法只有在指定的键已经存在并且有与之相关的映射值时才会将指定的键映射到指定的值(新值),javadoc的注释解释了该默认值⽅法的实现的等价java代码:
对于⽂中的map,该默认值实现是等价于新⽅法的:
if (ainsKey(key)) {
return map.put(key, value);
} else {
return null;
}
下⾯展⽰的是新⽅法和JDK8之前的⽅法⽐较:
/*
* ⽰范place(K, V)⽅法并和JDK8之前的实现⽅法做对⽐。JDK8
* 中新增的place(K, V)⽅法相⽐于传统的实现⽅法,所⽤的代码⾏数更少
* 并且允许⽤⼀个final类型的变量来接收返回值。
*/
// JDK8之前的实现⽅式
String replacedCapitalCity;
if (ainsKey("Alaska"))  {
replacedCapitalCity = statesAndCapitals.put("Alaska", "Juneau");
}
// JDK8的实现⽅式
final String replacedJdk8City = place("Alaska", "Juneau");
第⼆的新增的Map replace⽅法在替换现存值⽅⾯有更窄的释义范围。当那个⽅法(上⼀个replace⽅法)只是涵盖指定的键在映射中有任意⼀个有效的值的替换处理,⽽这个“replace”⽅法接受⼀个额外的(第三个)参数,只有在指定的键和值都匹配的情况下才会替换。
javadoc注释说明了该默认值⽅法的实现:
if (ainsKey(key) && Objects.(key), value)) {
map.put(key, newValue);
return true;
} else {
return false;
}
下⾯这段代码列举展⽰的是新实现⽅法和JDK8之前的实现⽅法的⼀个具体⽐较。
/*
* ⽰范place(K, V, V)⽅法并和JDK8之前的实现⽅法做对⽐。JDK8
* 中新增的place(K, V, V)⽅法相⽐于传统的实现⽅法,所⽤的代码⾏数更少
* 并且允许⽤⼀个final类型的变量来接收返回值。
*/
// JDK8之前的实现⽅式
boolean replaced = false;
if (  ainsKey("Nevada")
&& Objects.("Nevada"), "Las Vegas")) {
statesAndCapitals.put("Nevada", "Carson City");
replaced = true;
}
// JDK8的实现⽅式
final boolean replacedJdk8 = place("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实现突然⾃动的就可以访问这些新增的⽅法⽽不需要做任何代码变更。