Javaで2つのHashMapを比較する

Javaでの2つのHashMapの比較

1. 概要

このチュートリアルでは、we’re going to explore different ways to compare two HashMaps in Java

2つのHashMapsが類似しているかどうかを確認する複数の方法について説明します。 また、Java 8 Stream APIとGuavaを使用して、異なるHashMaps間の詳細な違いを取得します。

2. Map.equals()の使用

まず、Map.equals()を使用して、2つのHashMapsに同じエントリがあるかどうかを確認します。

@Test
public void whenCompareTwoHashMapsUsingEquals_thenSuccess() {
    Map asiaCapital1 = new HashMap();
    asiaCapital1.put("Japan", "Tokyo");
    asiaCapital1.put("South Korea", "Seoul");

    Map asiaCapital2 = new HashMap();
    asiaCapital2.put("South Korea", "Seoul");
    asiaCapital2.put("Japan", "Tokyo");

    Map asiaCapital3 = new HashMap();
    asiaCapital3.put("Japan", "Tokyo");
    asiaCapital3.put("China", "Beijing");

    assertTrue(asiaCapital1.equals(asiaCapital2));
    assertFalse(asiaCapital1.equals(asiaCapital3));
}

ここでは、3つのHashMapオブジェクトを作成し、エントリを追加しています。 次に、Map.equals()を使用して、2つのHashMapsに同じエントリがあるかどうかを確認します。

The way that Map.equals() works is by comparing keys and values using the Object.equals()method.これは、キーオブジェクトと値オブジェクトの両方がequals()を適切に実装している場合にのみ機能することを意味します。

たとえば、値の型が配列の場合、Map.equals()は機能しません。これは、配列のequals()メソッドが、配列の内容ではなくIDを比較するためです。

@Test
public void whenCompareTwoHashMapsWithArrayValuesUsingEquals_thenFail() {
    Map asiaCity1 = new HashMap();
    asiaCity1.put("Japan", new String[] { "Tokyo", "Osaka" });
    asiaCity1.put("South Korea", new String[] { "Seoul", "Busan" });

    Map asiaCity2 = new HashMap();
    asiaCity2.put("South Korea", new String[] { "Seoul", "Busan" });
    asiaCity2.put("Japan", new String[] { "Tokyo", "Osaka" });

    assertFalse(asiaCity1.equals(asiaCity2));
}

3. JavaStreamAPIの使用

Java 8Stream APIを使用してHashMapsを比較する独自のメソッドを実装することもできます。

private boolean areEqual(Map first, Map second) {
    if (first.size() != second.size()) {
        return false;
    }

    return first.entrySet().stream()
      .allMatch(e -> e.getValue().equals(second.get(e.getKey())));
}

簡単にするために、HashMap<String, String>オブジェクトを比較するために使用できるareEqual()メソッドを実装しました。

@Test
public void whenCompareTwoHashMapsUsingStreamAPI_thenSuccess() {
    assertTrue(areEqual(asiaCapital1, asiaCapital2));
    assertFalse(areEqual(asiaCapital1, asiaCapital3));
}

ただし、Arrays.equals()を使用して2つの配列を比較することにより、独自のメソッドareEqualWithArrayValue()をカスタマイズして配列値を処理することもできます。

private boolean areEqualWithArrayValue(Map first, Map second) {
    if (first.size() != second.size()) {
        return false;
    }

    return first.entrySet().stream()
      .allMatch(e -> Arrays.equals(e.getValue(), second.get(e.getKey())));
}

Map.equals()とは異なり、独自のメソッドはHashMapsを配列値と正常に比較します。

@Test
public void whenCompareTwoHashMapsWithArrayValuesUsingStreamAPI_thenSuccess() {
    assertTrue(areEqualWithArrayValue(asiaCity1, asiaCity2));
    assertFalse(areEqualWithArrayValue(asiaCity1, asiaCity3));
}

4. HashMapのキーと値の比較

次に、2つのHashMapキーとそれに対応する値を比較する方法を見てみましょう。

4.1. HashMapキーの比較

まず、2つのHashMapsが同じキーを持っているかどうかを、それらのKeySet()を比較するだけで確認できます。

@Test
public void whenCompareTwoHashMapKeys_thenSuccess() {
    assertTrue(asiaCapital1.keySet().equals(asiaCapital2.keySet()));
    assertFalse(asiaCapital1.keySet().equals(asiaCapital3.keySet()));
}

4.2. HashMap値の比較

次に、HashMapの値を1つずつ比較する方法を説明します。

Stream APIを使用して、両方のHashMapsで同じ値を持つキーを確認する簡単なメソッドを実装します。

private Map areEqualKeyValues(Map first, Map second) {
    return first.entrySet().stream()
      .collect(Collectors.toMap(e -> e.getKey(),
        e -> e.getValue().equals(second.get(e.getKey()))));
}

これで、areEqualKeyValues()を使用して2つの異なるHashMapsを比較し、どのキーの値が同じで、どのキーの値が異なるかを詳細に確認できます。

@Test
public void whenCompareTwoHashMapKeyValuesUsingStreamAPI_thenSuccess() {
    Map asiaCapital3 = new HashMap();
    asiaCapital3.put("Japan", "Tokyo");
    asiaCapital3.put("South Korea", "Seoul");
    asiaCapital3.put("China", "Beijing");

    Map asiaCapital4 = new HashMap();
    asiaCapital4.put("South Korea", "Seoul");
    asiaCapital4.put("Japan", "Osaka");
    asiaCapital4.put("China", "Beijing");

    Map result = areEqualKeyValues(asiaCapital3, asiaCapital4);

    assertEquals(3, result.size());
    assertThat(result, hasEntry("Japan", false));
    assertThat(result, hasEntry("South Korea", true));
    assertThat(result, hasEntry("China", true));
}

5. グアバを使用した地図の違い

最後に、how to get a detailed difference between two HashMaps using Guava Maps.difference().が表示されます

このメソッドは、Maps.間の違いを分析するためのいくつかの便利なメソッドを持つMapDifferenceオブジェクトを返します。これらのいくつかを見てみましょう。

5.1. MapDifference.entriesDiffering()

まず、common keys that have different values in each HashMap using MapDifference.entriesDiffering()を取得します。

@Test
public void givenDifferentMaps_whenGetDiffUsingGuava_thenSuccess() {
    Map asia1 = new HashMap();
    asia1.put("Japan", "Tokyo");
    asia1.put("South Korea", "Seoul");
    asia1.put("India", "New Delhi");

    Map asia2 = new HashMap();
    asia2.put("Japan", "Tokyo");
    asia2.put("China", "Beijing");
    asia2.put("India", "Delhi");

    MapDifference diff = Maps.difference(asia1, asia2);
    Map> entriesDiffering = diff.entriesDiffering();

    assertFalse(diff.areEqual());
    assertEquals(1, entriesDiffering.size());
    assertThat(entriesDiffering, hasKey("India"));
    assertEquals("New Delhi", entriesDiffering.get("India").leftValue());
    assertEquals("Delhi", entriesDiffering.get("India").rightValue());
}

entriesDiffering()メソッドは、共通キーのセットと値のセットとしてValueDifferenceオブジェクトを含む新しいMapを返します。

それぞれEach ValueDifference object has a leftValue() and rightValue() methods that return the values in the two Maps

5.2. MapDifference.entriesOnlyOnRight()およびMapDifference.entriesOnlyOnLeft()

次に、MapDifference.entriesOnlyOnRight()MapDifference.entriesOnlyOnLeft():を使用して、1つのHashMapにのみ存在するエントリを取得できます。

@Test
public void givenDifferentMaps_whenGetEntriesOnOneSideUsingGuava_thenSuccess() {
    MapDifference diff = Maps.difference(asia1, asia2);
    Map entriesOnlyOnRight = diff.entriesOnlyOnRight();
    Map entriesOnlyOnLeft = diff.entriesOnlyOnLeft();

    assertEquals(1, entriesOnlyOnRight.size());
    assertEquals(1, entriesOnlyOnLeft.size());
    assertThat(entriesOnlyOnRight, hasEntry("China", "Beijing"));
    assertThat(entriesOnlyOnLeft, hasEntry("South Korea", "Seoul"));
}

5.3. MapDifference.entriesInCommon()

次に、we’ll get common entries using MapDifference.entriesInCommon():

@Test
public void givenDifferentMaps_whenGetCommonEntriesUsingGuava_thenSuccess() {
    MapDifference diff = Maps.difference(asia1, asia2);
    Map entriesInCommon = diff.entriesInCommon();

    assertEquals(1, entriesInCommon.size());
    assertThat(entriesInCommon, hasEntry("Japan", "Tokyo"));
}

5.4. Maps.difference()の動作のカスタマイズ

Maps.difference()はデフォルトでequals()hashCode()を使用してエントリを比較するため、それらを適切に実装していないオブジェクトでは機能しません。

@Test
public void givenSimilarMapsWithArrayValue_whenCompareUsingGuava_thenFail() {
    MapDifference diff = Maps.difference(asiaCity1, asiaCity2);
    assertFalse(diff.areEqual());
}

ただし、we can customize the method used in comparison using Equivalence

たとえば、タイプString[]Equivalence を定義して、HashMapsString[]の値を好きなように比較します。

@Test
public void givenSimilarMapsWithArrayValue_whenCompareUsingGuavaEquivalence_thenSuccess() {
    Equivalence eq = new Equivalence() {
        @Override
        protected boolean doEquivalent(String[] a, String[] b) {
            return Arrays.equals(a, b);
        }

        @Override
        protected int doHash(String[] value) {
            return value.hashCode();
        }
    };

    MapDifference diff = Maps.difference(asiaCity1, asiaCity2, eq);
    assertTrue(diff.areEqual());

    diff = Maps.difference(asiaCity1, asiaCity3, eq);
    assertFalse(diff.areEqual());
}

6. 結論

この記事では、JavaでHashMapsを比較するさまざまな方法について説明しました。 2つのHashMapsが等しいかどうかを確認する複数の方法と、詳細な違いを取得する方法も学びました。

完全なソースコードはover on GitHubで入手できます。