在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個洲的一個小子集。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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中是如何實現的。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/*
* 示范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中已經公布了它的默認實現的等價代碼:
|
1
2
3
4
5
|
V v = map.get(key);
if
(v ==
null
)
v = map.put(key, value);
return
v;
|
這在另一段對比JDK8之前的實現方法和JDK8的實現方法的代碼示例中得到了證明。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/*
* 示范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,該默認值實現是等價於新方法的:
|
1
2
3
4
5
6
|
if
(map.containsKey(key) && Objects.equals(map.get(key), value)) {
map.remove(key);
return
true
;
}
else
{
return
false
;
}
|
下面這段代碼列舉展示的是新實現方法和JDK8之前的實現方法的一個具體比較。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/*
* 示范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,該默認值實現是等價於新方法的:
|
1
2
3
4
5
|
if
(map.containsKey(key)) {
return
map.put(key, value);
}
else
{
return
null
;
}
|
下面展示的是新方法和JDK8之前的方法比較:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/*
* 示范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注釋說明了該默認值方法的實現:
|
1
2
3
4
5
6
|
if
(map.containsKey(key) && Objects.equals(map.get(key), value)) {
map.put(key, newValue);
return
true
;
}
else
{
return
false
;
}
|
下面這段代碼列舉展示的是新實現方法和JDK8之前的實現方法的一個具體比較。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/*
* 示范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實現突然自動的就可以訪問這些新增的方法而不需要做任何代碼變更。
