web-dev-qa-db-ja.com

2つのマップの比較

Map<String, Object>として宣言された2つのマップがあります。ここのObjectは別のMap<String, Object>(など)である可能性があります。 2つのマップがその深さを知らずにまったく同じであるかどうかを確認したいと思います。再帰を使用する代わりに、各マップで呼び出されるtoString()の出力を比較できますか?または、マップを比較する簡単な方法はありますか?

58
noMAD

素早い回答

必要な比較を実行するために実装されているため、equalsメソッドを使用する必要があります。 toString()自体は、equalsと同じようにイテレーターを使用しますが、より非効率的なアプローチです。さらに、@ Teepeemmが指摘したように、toStringは要素の順序(基本的にイテレータの戻り順序)の影響を受けるため、2つの異なるマップに対して同じ出力を提供することは保証されません(特に2つの異なるマップを比較する場合)。

Note/Warning:あなたの質問と私の答えは、マップインターフェイスを実装するクラスが、期待されるtoStringequalsの振る舞いを尊重すると仮定しています。デフォルトのJavaクラスはそうしますが、予想される動作を確認するには、カスタムマップクラスを調べる必要があります。

参照: http://docs.Oracle.com/javase/7/docs/api/Java/util/Map.html

boolean equals(Object o)

指定されたオブジェクトとこのマップが等しいかどうかを比較します。指定されたオブジェクトもマップであり、2つのマップが同じマッピングを表す場合、trueを返します。より正式には、2つのマップm1とm2は、m1.entrySet()。equals(m2.entrySet())の場合、同じマッピングを表します。これにより、Mapsインターフェイスのさまざまな実装でequalsメソッドが適切に機能することが保証されます。

Java Sourceでの実装(Java.util.AbstractMap)

さらに、Java自体がすべての要素を反復処理し、比較を行うため、必要はありません。 AbstractMapなどのクラスで使用されるHashMapの実装を見てください。

 // Comparison and hashing

    /**
     * Compares the specified object with this map for equality.  Returns
     * <tt>true</tt> if the given object is also a map and the two maps
     * represent the same mappings.  More formally, two maps <tt>m1</tt> and
     * <tt>m2</tt> represent the same mappings if
     * <tt>m1.entrySet().equals(m2.entrySet())</tt>.  This ensures that the
     * <tt>equals</tt> method works properly across different implementations
     * of the <tt>Map</tt> interface.
     *
     * <p>This implementation first checks if the specified object is this map;
     * if so it returns <tt>true</tt>.  Then, it checks if the specified
     * object is a map whose size is identical to the size of this map; if
     * not, it returns <tt>false</tt>.  If so, it iterates over this map's
     * <tt>entrySet</tt> collection, and checks that the specified map
     * contains each mapping that this map contains.  If the specified map
     * fails to contain such a mapping, <tt>false</tt> is returned.  If the
     * iteration completes, <tt>true</tt> is returned.
     *
     * @param o object to be compared for equality with this map
     * @return <tt>true</tt> if the specified object is equal to this map
     */
    public boolean equals(Object o) {
        if (o == this)
            return true;

        if (!(o instanceof Map))
            return false;
        Map<K,V> m = (Map<K,V>) o;
        if (m.size() != size())
            return false;

        try {
            Iterator<Entry<K,V>> i = entrySet().iterator();
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                K key = e.getKey();
                V value = e.getValue();
                if (value == null) {
                    if (!(m.get(key)==null && m.containsKey(key)))
                        return false;
                } else {
                    if (!value.equals(m.get(key)))
                        return false;
                }
            }
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }

        return true;
    }

2種類のマップの比較

toStringは内容を正しく比較しますが、TreeMapHashMapを比較すると、equalsは惨めに失敗します。

コード:

public static void main(String args[]) {
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("2", "whatever2");
map.put("1", "whatever1");
TreeMap<String, Object> map2 = new TreeMap<String, Object>();
map2.put("2", "whatever2");
map2.put("1", "whatever1");

System.out.println("Are maps equal (using equals):" + map.equals(map2));
System.out.println("Are maps equal (using toString().equals()):"
        + map.toString().equals(map2.toString()));

System.out.println("Map1:"+map.toString());
System.out.println("Map2:"+map2.toString());
}

出力:

Are maps equal (using equals):true
Are maps equal (using toString().equals()):false
Map1:{2=whatever2, 1=whatever1}
Map2:{1=whatever1, 2=whatever2}
60

マップに含まれる各キーと値でequals()をオーバーライドする限り、m1.equals(m2)はマップの同等性をチェックするために信頼できるはずです。

あなたが提案したように各マップのtoString()を比較することでも同じ結果が得られますが、equals()を使用する方がより直感的なアプローチです。

特定の状況ではないかもしれませんが、配列をマップに格納する場合、値ごとに、またはArrays.equals()を使用して比較する必要があるため、少し注意が必要です。これについての詳細は here をご覧ください。

11
niculare