Map/Set 对象图的奇怪 equals() 结果
Weird equals() result with Map/Set object graph
调查一个特殊情况,其中某些对象不等于它们应有的值,并得出这个简化我的问题的简单测试用例。
当 运行 在 Eclipse 中使用 JUnit 和 jdk8u152 最后一个 assertEquals 失败时,谁能解释为什么?
这与 Set/HashSet 相关,因为如果我将 as,bs 更改为 ArrayList,最终的 assertEquals 就会通过。
@Test
public void test()
{
String list = "list";
String object = "object";
String value = "value";
Map<String, Object> a = new HashMap<>();
Map<String, Object> b = new HashMap<>();
assertEquals(a, b);
Set<Object> as = new HashSet<>();
Set<Object> bs = new HashSet<>();
a.put(list, as);
b.put(list, bs);
assertEquals(a, b);
Map<String, Object> ao = new HashMap<>();
as.add(ao);
Map<String, Object> bo = new HashMap<>();
bs.add(bo);
assertEquals(a, b);
ao.put(object, value);
bo.put(object, value);
assertEquals(a, b);
}
您正在改变集合的元素。这会导致未指定的行为。
来自JavaDoc:
Great care must be exercised if mutable objects are used as set elements. The behavior of a set is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is an element in the set.
这是因为 HashMap 的 hascode 实现基本上是键和值的异或。如果 key 或 value 为空,则 hascode 将为零。因此,所有空哈希映射的哈希码都将为零。
/*hashcode of HashMap*/
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
/*hashcode of object*/
public static int hashCode(Object o) {
return o != null ? o.hashCode() : 0;
}
添加键值对后,哈希码值会发生变化。
您正在将 ao
和 bo
HashMap
添加到 HashSet
as
和 bs
。
稍后您通过在每个 ao
和 bo
中放置一个新条目来改变它们。
这意味着在as
中用于放置ao
的hashCode
不再是ao
的当前hashCode
,并且hashCode
用于在 bs
中放置 bo
不再是 bo
的当前 hashCode
。
结果AbstractSet
的equals
无法在另一个Set
中定位一个Set
的元素,所以得出as
不等于 bs
。结果 a
不等于 b
.
这里是 AbstractSet
的 equals
的实现。你可以看到它使用 containsAll
,它又调用 contains()
,它依赖于搜索元素的 hashCode
。由于 hashCode
在元素添加到 Set
后发生了变化,contains()
找不到该元素。
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Set))
return false;
Collection<?> c = (Collection<?>) o;
if (c.size() != size())
return false;
try {
return containsAll(c);
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
}
如果您以影响 equals
或 hashCode
结果的方式改变 HashSet
的元素,则必须先从 HashSet
中删除该元素到更新,更新后再添加。
添加以下 remove
和 add
调用将导致 a
最后等于 b
:
....
assertEquals(a, b);
bs.remove (bo); // added
as.remove (ao); // added
ao.put(object, value);
bo.put(object, value);
as.add (ao); // added
bs.add (bo); // added
assertEquals(a, b);
调查一个特殊情况,其中某些对象不等于它们应有的值,并得出这个简化我的问题的简单测试用例。
当 运行 在 Eclipse 中使用 JUnit 和 jdk8u152 最后一个 assertEquals 失败时,谁能解释为什么?
这与 Set/HashSet 相关,因为如果我将 as,bs 更改为 ArrayList,最终的 assertEquals 就会通过。
@Test
public void test()
{
String list = "list";
String object = "object";
String value = "value";
Map<String, Object> a = new HashMap<>();
Map<String, Object> b = new HashMap<>();
assertEquals(a, b);
Set<Object> as = new HashSet<>();
Set<Object> bs = new HashSet<>();
a.put(list, as);
b.put(list, bs);
assertEquals(a, b);
Map<String, Object> ao = new HashMap<>();
as.add(ao);
Map<String, Object> bo = new HashMap<>();
bs.add(bo);
assertEquals(a, b);
ao.put(object, value);
bo.put(object, value);
assertEquals(a, b);
}
您正在改变集合的元素。这会导致未指定的行为。
来自JavaDoc:
Great care must be exercised if mutable objects are used as set elements. The behavior of a set is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is an element in the set.
这是因为 HashMap 的 hascode 实现基本上是键和值的异或。如果 key 或 value 为空,则 hascode 将为零。因此,所有空哈希映射的哈希码都将为零。
/*hashcode of HashMap*/
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
/*hashcode of object*/
public static int hashCode(Object o) {
return o != null ? o.hashCode() : 0;
}
添加键值对后,哈希码值会发生变化。
您正在将 ao
和 bo
HashMap
添加到 HashSet
as
和 bs
。
稍后您通过在每个 ao
和 bo
中放置一个新条目来改变它们。
这意味着在as
中用于放置ao
的hashCode
不再是ao
的当前hashCode
,并且hashCode
用于在 bs
中放置 bo
不再是 bo
的当前 hashCode
。
结果AbstractSet
的equals
无法在另一个Set
中定位一个Set
的元素,所以得出as
不等于 bs
。结果 a
不等于 b
.
这里是 AbstractSet
的 equals
的实现。你可以看到它使用 containsAll
,它又调用 contains()
,它依赖于搜索元素的 hashCode
。由于 hashCode
在元素添加到 Set
后发生了变化,contains()
找不到该元素。
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Set))
return false;
Collection<?> c = (Collection<?>) o;
if (c.size() != size())
return false;
try {
return containsAll(c);
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
}
如果您以影响 equals
或 hashCode
结果的方式改变 HashSet
的元素,则必须先从 HashSet
中删除该元素到更新,更新后再添加。
添加以下 remove
和 add
调用将导致 a
最后等于 b
:
....
assertEquals(a, b);
bs.remove (bo); // added
as.remove (ao); // added
ao.put(object, value);
bo.put(object, value);
as.add (ao); // added
bs.add (bo); // added
assertEquals(a, b);