いまさらながら
先日Java 9がリリースされたにもかかわらず敢えてこのタイミングでJava 8を勉強し始めました。
周回遅れもいいところなのですが、今更ながらJava 8の新機能はステキすぎて感動してしまいます。
Java 8の新機能についての解説はググると分かりやすい記事がいくつも見つかりますので、全体の解説はそれらに譲るとして、ここでは私が分かりづらかった&あまり細かく解説している記事がないMap#mergeの動きについてメモしておきます。
Map#merge
「Map内にキー"key1"が存在していれば値を"CCC"で上書き、存在してなければ新しいキーとして値"BBB"を追加する」という処理を、Java 7までとJava 8での書き方を比べてみます。
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "AAA");
System.out.println(map);
if (map.containsKey("key1")) {
map.put("key1", "CCC");
} else {
map.put("key1", "BBB");
}
System.out.println(map);
{key1=AAA}
{key1=CCC}
Map<String, String> map = new HashMap<String, String>();
System.out.println(map);
if (map.containsKey("key1")) {
map.put("key1", "CCC");
} else {
map.put("key1", "BBB");
}
System.out.println(map);
{}
{key1=BBB}
Java 8で追加になったMap#mergeを使ってみると以下のようになります。
Mapに第1引数に指定したキー("key1")が存在しない場合は第2引数の値("BBB")がそのキー("key1")でputされ、キーが存在する場合は第3引数のremapping関数が実行されます。
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "AAA");
System.out.println(map);
map.merge("key1", "BBB",
(v1, v2) -> {
return "CCC";
});
System.out.println(map);
{key1=AAA}
{key1=CCC}
Map<String, String> map = new HashMap<String, String>();
System.out.println(map);
map.merge("key1", "BBB",
(v1, v2) -> {
return "CCC";
});
System.out.println(map);
{}
{key1=BBB}
2つの引数は?
ここで疑問に思ったのが、Mapにキーが存在するときに呼ばれるremapping関数に渡される2つの引数は何?ということです。上の例のv1
とv2
ですが、全く使っていません。
ということでとりあえず何が渡されているのか出力してみます。
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "AAA");
System.out.println(map);
map.merge("key1", "BBB",
(v1, v2) -> {
Sytem.out.printf("v1:%s, v2:%s\n", v1, v2);
return "CCC";
});
System.out.println(map);
{key1=AAA}
v1:AAA, v2:BBB
{key1=CCC}
となりました。
つまりキー"key1"が存在していたとき、既に存在する値(例では"AAA")と存在しない場合に設定する値(例では"BBB")がこの関数に渡されます。
ただ「キーが存在しない場合に設定する値」が引数で渡されたとしても、キーが存在していた時に使われるケースは少ないのではないかと思うのです。
この値が渡されることで役に立ちそうなのは、例えばこの値を既存の値(文字列)に連結するときなど限られた用途になるのかもしれません。今回のような単純な置き換えの場合はJava 7までの書き方の方がむしろ分かりやすいですね。
ちなみに単純な連結であれば以下のようにremapping関数にString::concat
を指定することで実現できます。
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "AAA");
System.out.println(map);
map.merge("key1", "BBB", String::concat);
System.out.println(map);
{key1=AAA}
{key1=AAABBB}
なぜmergeを調べたのか
今回Map#mergeの動きを詳しく調べたのは、Map<String, List<String>>
の操作でキーが存在する場合にListに値を追加するということをJava 8の新機能でスマートにできないかと考えたことがきっかけでした。具体的には以下のような感じで実装しました。
Map<String, List<String>> map = new HashMap<String, List<String>>();
map.put("key1", new ArrayList<String>(Arrays.asList("AAA")));
System.out.println(map);
// key1が存在すれば"CCC"をListに追加、存在しなければ"BBB"という要素を持ったListを追加
map.merge("key1", new ArrayList<String>(Arrays.asList("BBB")),
(v1, v2) -> {
v1.add("CCC");
return v1;
});
System.out.println(map);
{key1=[AAA]}
{key1=[AAA, CCC]}
Java 7以前だと以下のような感じです。
Map<String, List<String>> map = new HashMap<String, List<String>>();
map.put("key1", new ArrayList<String>(Arrays.asList("AAA")));
System.out.println(map);
if (map.containsKey("key1")) {
// key1が存在すれば"CCC"をListに追加
map.get("key1").add("CCC");
} else {
// key1が存在しなければ"BBB"という要素を持ったListを追加
map.put("key1", new ArrayList<String>(Arrays.asList("BBB")));
}
System.out.println(map);
{key1=[AAA]}
{key1=[AAA, CCC]}
この例だとどちらも大して変わらないですね(;^ω^)
ただJava 8だとif-else文を無くせるので気持ちスッキリしました。
ということでMap#mergeが活躍するのは、「既にMap内に存在する値に対して何か変更を加えるとき」や「コードをスッキリさせたいとき」でしょうか。後者はこのメソッドに限らずラムダ式やStreamを使う理由でもありますが。
おまけ
余談ですがremapping関数でnullを返せばそのキーは削除されます。
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "AAA");
System.out.println(map);
map.merge("key1", "BBB",
(v1, v2) -> {
return null;
});
System.out.println(map);
{key1=AAA}
{}
もう少しJava 8で遊んでみることにします^^