1. 概要

このチュートリアルでは、HashMapの特定のキーに関連付けられた値を更新するためのさまざまなアプローチについて説明します。 最初に、Java8より前に利用可能だった機能のみを使用するいくつかの一般的なソリューションを見ていきます。 次に、Java8以降で利用可能ないくつかの追加ソリューションを見ていきます。

2. 例の初期化HashMap

HashMap の値を更新する方法を示すには、最初に値を作成して入力する必要があります。 そこで、果物をキーとして、価格を値としてマップを作成します。

Map<String, Double> priceMap = new HashMap<>();
priceMap.put("apple", 2.45);
priceMap.put("grapes", 1.22);

この例では、このHashMapを使用します。 これで、HashMapキーに関連付けられた値を更新する方法を理解する準備が整いました。

3. Java8より前

Java8より前に利用可能だったメソッドから始めましょう。

3.1. putメソッド

putメソッドは、値を更新するか、新しいエントリを追加します。 すでに存在するキーで使用される場合、putメソッドは関連する値を更新します。 それ以外の場合は、新しい(キー、値)ペアが追加されます。

このメソッドの動作を2つの簡単な例でテストしてみましょう。

@Test
public void givenFruitMap_whenPuttingAList_thenHashMapUpdatesAndInsertsValues() {
    Double newValue = 2.11;
    fruitMap.put("apple", newValue);
    fruitMap.put("orange", newValue);
    
    Assertions.assertEquals(newValue, fruitMap.get("apple"));
    Assertions.assertTrue(fruitMap.containsKey("orange"));
    Assertions.assertEquals(newValue, fruitMap.get("orange"));
}

キーappleはすでにマップ上にあります。 したがって、最初のアサーションは合格します。

orange はマップに存在しないため、putメソッドが追加します。 したがって、他の2つのアサーションも通過します。

3.2. containsKeyおよびputメソッドの組み合わせ

containsKeyメソッドとputメソッドの組み合わせは、HashMapのキーの値を更新するもう1つの方法です。 このオプションは、マップにすでにキーが含まれているかどうかを確認します。 このような場合、putメソッドを使用して値を更新できます 。 それ以外の場合は、マップにエントリを追加するか、何もしません。

この例では、簡単なテストでこのアプローチを検証します。

@Test
public void givenFruitMap_whenKeyExists_thenValuesUpdated() {
    double newValue = 2.31;
    if (fruitMap.containsKey("apple")) {
        fruitMap.put("apple", newValue);
    }
    
    Assertions.assertEquals(Double.valueOf(newValue), fruitMap.get("apple"));
}

apple がマップ上にあるため、containsKeyメソッドはtrueを返します。 したがって、 put メソッドの呼び出しが実行され、値が更新されます。

4. Java8以降

Java 8以降、 HashMapのキーの値を更新するプロセスを容易にする多くの新しいメソッドが利用可能になりました。では、それらについて理解しましょう。

4.1. 置換メソッド

バージョン8以降、Mapインターフェイスで2つのオーバーロードされたreplaceメソッドが使用可能になりました。 メソッドのシグネチャを見てみましょう。

public V replace(K key, V value);
public boolean replace(K key, V oldValue, V newValue);

最初のreplaceメソッドは、キーと新しい値のみを取ります。 また、古い値を返します。

メソッドがどのように機能するかを見てみましょう。

@Test
public void givenFruitMap_whenReplacingOldValue_thenNewValueSet() {
    double newPrice = 3.22;
    Double applePrice = fruitMap.get("apple");
    
    Double oldValue = fruitMap.replace("apple", newPrice);
    
    Assertions.assertNotNull(oldValue);
    Assertions.assertEquals(oldValue, applePrice);
    Assertions.assertEquals(Double.valueOf(newPrice), fruitMap.get("apple"));
}

キーappleの値は、replaceメソッドを使用して新しい価格に更新されます。 したがって、2番目と3番目のアサーションは通過します。

ただし、最初のアサーションは興味深いです。 HashMapにキーapple がなかった場合はどうなりますか? 存在しないキーの値を更新しようとすると、nullが返されます。 それを考慮に入れると、別の疑問が生じます。 ヌル価値? replace メソッドから返された値が実際に提供されたキーの値であったかどうか、または存在しないキーの値を更新しようとしたかどうかはわかりません。

したがって、誤解を避けるために、2番目のreplaceメソッドを使用できます。 それは3つの引数を取ります:

  • かぎ
  • キーに関連付けられている現在の値
  • キーに関連付ける新しい値

次の1つの条件で、キーの値を新しい値に更新します。 2番目の引数が現在の値の場合、キー値は新しい値に更新されます。 更新が成功すると、メソッドはtrueを返します。 それ以外の場合は、falseが返されます。

それでは、2番目のreplaceメソッドをチェックするためにいくつかのテストを実装しましょう。

@Test
public void givenFruitMap_whenReplacingWithRealOldValue_thenNewValueSet() {
    double newPrice = 3.22;
    Double applePrice = fruitMap.get("apple");
    
    boolean isUpdated = fruitMap.replace("apple", applePrice, newPrice);
    
    Assertions.assertTrue(isUpdated);
}

@Test
public void givenFruitMap_whenReplacingWithWrongOldValue_thenNewValueNotSet() {
    double newPrice = 3.22;
    boolean isUpdated = fruitMap.replace("apple", Double.valueOf(0), newPrice);
    
    Assertions.assertFalse(isUpdated);
}

最初のテストではreplaceメソッドがキーの現在の値で呼び出されるため、その値が置き換えられます。

一方、2番目のテストは現在の値では呼び出されません。 したがって、falseが返されます。

4.2. getOrDefaultputMethodsの組み合わせ

提供されたキーのエントリがない場合は、getOrDefaultメソッドが最適です。 その場合、存在しないキーのデフォルト値を設定します。 次に、エントリがマップに追加されます。 このアプローチでは、NullPointerExceptionを簡単に回避できます。

元々マップ上にないキーを使用して、この組み合わせを試してみましょう。

@Test
public void givenFruitMap_whenGetOrDefaultUsedWithPut_thenNewEntriesAdded() {
    fruitMap.put("plum", fruitMap.getOrDefault("plum", 2.41));
    
    Assertions.assertTrue(fruitMap.containsKey("plum"));
    Assertions.assertEquals(Double.valueOf(2.41), fruitMap.get("plum"));
}

そのようなキーがないため、getOrDefaultメソッドはデフォルト値を返します。 次に、 put メソッドは、新しい(キー、値)ペアを追加します。 したがって、すべてのアサーションが合格します。

4.3. putIfAbsentメソッド

putIfAbsent メソッドは、getOrDefaultメソッドとputメソッドの以前の組み合わせと同じように機能します。

提供されたキーを持つHashMapにペアがない場合、putIfAbsentメソッドはペアを追加します。 ただし、そのようなペアがある場合、putIfAbsentメソッドはマップを変更しません。

ただし、例外があります。既存のペアの値がnullの場合、ペアは新しい値に更新されます。

putIfAbsentメソッドのテストを実装しましょう。 2つの例で動作をテストします。

@Test
public void givenFruitMap_whenPutIfAbsentUsed_thenNewEntriesAdded() {
    double newValue = 1.78;
    fruitMap.putIfAbsent("apple", newValue);
    fruitMap.putIfAbsent("pear", newValue);
    
    Assertions.assertTrue(fruitMap.containsKey("pear"));
    Assertions.assertNotEquals(Double.valueOf(newValue), fruitMap.get("apple"));
    Assertions.assertEquals(Double.valueOf(newValue), fruitMap.get("pear"));
}

キーアップルがマップに存在します。putIfAbsentメソッドは現在の値を変更しません。

同時に、キーpearがマップから欠落しています。 したがって、追加されます

4.4. 計算メソッド

計算メソッドは、2番目のパラメーターとして提供されたBiFunctionに基づいてキーの値を更新します。 キーがマップ上に存在しない場合、NullPointerExceptionが発生する可能性があります。

簡単なテストでこのメソッドの動作を確認しましょう。

@Test
public void givenFruitMap_whenComputeUsed_thenValueUpdated() {
    double oldPrice = fruitMap.get("apple");
    BiFunction<Double, Integer, Double> powFunction = (x1, x2) -> Math.pow(x1, x2);
    
    fruitMap.compute("apple", (k, v) -> powFunction.apply(v, 2));
    
    Assertions.assertEquals(
      Double.valueOf(Math.pow(oldPrice, 2)), fruitMap.get("apple"));
    
    Assertions.assertThrows(
      NullPointerException.class, () -> fruitMap.compute("blueberry", (k, v) -> powFunction.apply(v, 2)));
}

予想通り、キー apple が存在するため、マップ内のその値が更新されます。 一方、キー blueberry がないため、最後のアサーションで compute メソッドを2回呼び出すと、NullPointerExceptionが発生します。

4.5. computeIfAbsentメソッド

前のメソッドは、特定のキーの HashMap にペアがない場合、例外をスローします。 computeIfAbsent メソッドは、(キー、値)ペアが存在しない場合は、それを追加してマップを更新します

このメソッドの動作をテストしてみましょう。

@Test
public void givenFruitMap_whenComputeIfAbsentUsed_thenNewEntriesAdded() {
    fruitMap.computeIfAbsent("lemon", k -> Double.valueOf(k.length()));
    
    Assertions.assertTrue(fruitMap.containsKey("lemon"));
    Assertions.assertEquals(Double.valueOf("lemon".length()), fruitMap.get("lemon"));
}

キーレモンはマップ上に存在しません。 したがって、 comp uteIfAbsentメソッドはエントリを追加します。

4.6. computeIfPresentメソッド

computeIfPresent メソッドは、キーがHashMap に存在する場合、その値を更新します。

この方法をどのように使用できるか見てみましょう。

@Test
public void givenFruitMap_whenComputeIfPresentUsed_thenValuesUpdated() {
    Double oldAppleValue = fruitMap.get("apple");
    BiFunction<Double, Integer, Double> powFunction = (x1, x2) -> Math.pow(x1, x2);
    
    fruitMap.computeIfPresent("apple", (k, v) -> powFunction.apply(v, 2));
    
    Assertions.assertEquals(Double.valueOf(Math.pow(oldAppleValue, 2)), fruitMap.get("apple"));
}

キーappleがマップ内にあるため、アサーションは通過し、computeIfPresentメソッドはBiFunctionに従って値を更新します。

4.7. マージメソッド

The マージ方法そのようなキーがある場合は、BiFunctionを使用してHashMapのキーの値を更新します。 それ以外の場合は、メソッドの2番目の引数として指定された値に値が設定された新しい(キー、値)ペアが追加されます。

それでは、このメソッドの動作を調べてみましょう。

@Test
public void givenFruitMap_whenMergeUsed_thenNewEntriesAdded() {
    double defaultValue = 1.25;
    BiFunction<Double, Integer, Double> powFunction = (x1, x2) -> Math.pow(x1, x2);
    
    fruitMap.merge("apple", defaultValue, (k, v) -> powFunction.apply(v, 2));
    fruitMap.merge("strawberry", defaultValue, (k, v) -> powFunction.apply(v, 2));
    
    Assertions.assertTrue(fruitMap.containsKey("strawberry"));
    Assertions.assertEquals(Double.valueOf(defaultValue), fruitMap.get("strawberry"));
    Assertions.assertEquals(Double.valueOf(Math.pow(defaultValue, 2)), fruitMap.get("apple"));
}

テストでは、最初にキーappleに対してmergeメソッドを実行します。 すでにマップ上にあるので、値が変わります。 これは、メソッドに渡したdefaultValueパラメーターの2乗になります。

キーイチゴはマップに存在しません。 したがって、 merge メソッドは、defaultValueを値として追加します。

5. 結論

この記事では、HashMapのキーに関連付けられた値を更新するいくつかの方法について説明しました。

まず、最も一般的なアプローチから始めました。 次に、Java8以降で使用できるいくつかのメソッドを示しました。

いつものように、これらの例のコードはGitHubから入手できます。