У меня есть две карты, объявленные как 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));
}