Сравнение двух карт

У меня есть две карты, объявленные как Map<String, Object>. Object здесь может быть другим Map<String, Object> (и т.д.). Я хочу проверить, являются ли две карты одинаковыми, не зная их глубины. Вместо использования рекурсии я могу сравнить вывод toString(), вызываемый на каждой карте? Или есть более простой способ сравнить карты?

Ответ 1

Быстрый ответ

Вы должны использовать метод equals, так как это реализовано для выполнения нужного сравнения. toString() сам использует итератор точно так же, как equals, но это более неэффективный подход. Кроме того, как отметил @Teepeemm, toString влияет на порядок элементов (в основном порядок возврата итератора), следовательно, не гарантируется предоставление одинакового вывода для двух разных карт (особенно, если мы сравниваем две разные карты).

Примечание/Предупреждение. В вашем вопросе и моем ответе предполагается, что классы, реализующие интерфейс карты, будут уважать ожидаемое поведение toString и equals. Классы java по умолчанию делают это, но для проверки ожидаемого поведения необходимо изучить собственный класс карты.

Смотрите: http://docs.oracle.com/javase/7/docs/api/java/util/Map.html

boolean equals(Object o)

Сравнивает указанный объект с этой картой для равенства. Возвращает true если данный объект также является картой, а две карты представляют один и тот же отображения. Более формально две карты m1 и m2 представляют одинаковые сопоставления, если m1.entrySet(). equals (m2.entrySet()). Это гарантирует, что 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;
    }

Сравнение двух разных типов карт

toString терпит неудачу при сравнении TreeMap и HashMap, хотя 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}

Ответ 2

Пока вы переопределяете equals() на каждый ключ и значение, содержащееся на карте, тогда m1.equals(m2) должно быть надежным для проверки равенства карт.

Тот же результат можно получить и путем сравнения toString() каждой карты, как вы предполагали, но использование equals() - более интуитивный подход.

Может быть, не ваша конкретная ситуация, но если вы храните массивы на карте, может быть немного сложно, потому что их нужно сравнивать по значению или использовать Arrays.equals(). Подробнее об этом см. здесь.

Ответ 3

Я сделал этот тест и работает:

 import java.util.HashMap; import java.util.Map;
 import java.util.TreeMap; import org.junit.Assert;
 import org.junit.Test;
 ...
 private void put(String key, String value, Map<String,String> map1, Map<String, String> map2){
    map1.put(key, value);
    map2.put(key, value);
}

@Test
public void testEqualsMap() throws Exception {
    Map<String, String> hashmap = new HashMap<String, String>();
    Map<String, String> treemap = new TreeMap<String, String>();
    put("voltage", "110/220", treemap, hashmap);
    put("color", "blue", treemap, hashmap);
    Assert.assertTrue(hashmap.equals(treemap));

    treemap.put("color", "red");
    Assert.assertFalse(hashmap.equals(treemap));
}