Lau*_*fel 0 java equals hashset
这是Java 8中发生奇怪行为的代码段:
@Test
public void test() {
DummyPojo dummyPojo1 = DummyPojo.of("1", "A");
DummyPojo dummyPojo2 = DummyPojo.of("2", "B");
Set<DummyPojo> set1 = new HashSet<>();
set1.add(dummyPojo1);
Set<DummyPojo> set2 = new HashSet<>();
set2.add(dummyPojo2);
System.out.println("dummyPojo1 == dummyPojo2 should be false = " + (dummyPojo1.equals(dummyPojo2)));
System.out.println("set1 == set2 should be false = " + (set1.equals(set2)));
dummyPojo1.setAttribute1(dummyPojo2.getAttribute1());
dummyPojo1.setAttribute2(dummyPojo2.getAttribute2());
System.out.println("dummyPojo1 == dummyPojo2 should be true = " + (dummyPojo1.equals(dummyPojo2)));
System.out.println("set1 == set2 should be true = " + (set1.equals(set2)));//WRONG
System.out.println("set2 == set1 should be true = " + (set2.equals(set1)));//Breaking of Object#equals symmetry
}
@Data
public static class DummyPojo {
private String attribute1;
private String attribute2;
public static DummyPojo of(String attribute1, String attribute2) {
DummyPojo dummyPojo = new DummyPojo();
dummyPojo.attribute1 = attribute1;
dummyPojo.attribute2 = attribute2;
return dummyPojo;
}
}
Run Code Online (Sandbox Code Playgroud)
结果如下:
dummyPojo1 == dummyPojo2 should be false = false
set1 == set2 should be false = false
dummyPojo1 == dummyPojo2 should be true = true
set1 == set2 should be true = false
set2 == set1 should be true = true
Run Code Online (Sandbox Code Playgroud)
插入后修改集合中的元素会具有此行为(请注意,lombok批注@Data确实实现了equals和hashcode方法)。原因是在将元素添加到集合中时,会将其插入支持地图的hashMap的节点表中。为此,它通过哈希码计算索引。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null) //<-- HERE table[15 & hashcode] when first element
tab[i] = newNode(hash, key, value, null);
.
.
.
}
Run Code Online (Sandbox Code Playgroud)
但是,当检查它是否包含该元素时,它将假定表中的位置相同:
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {//<-- HERE table[15 & hashcode]
.
.
.
}
Run Code Online (Sandbox Code Playgroud)
但是,由于此示例中的元素不是不可变的,因此哈希码已在插入和包含之间更改。因此,即使元素根据其equals方法等于,因此集合包含相同的元素,集合上的equals也会返回意外值。
而且,如此处所示,它打破了Object#equals的javadoc中所述的equals对称性:
它是对称的:对于任何非null的引用值
x和y,当且仅当x.equals(y)return时应返回。truey.equals(x)true
有一个Java错误报告它:https : //bugs.java.com/bugdatabase/view_bug.do?bug_id=6579200
答案是 :
HashSet和HashMap的约定不允许hashCode()的结果在集合中某个对象的使用期限内更改,也不允许与该对象相等的一组对象在使用期限内更改。
但是,我没有在HashMap或HashSet的javadoc中找到它。一个不了解这种行为的人可能对此表示过多的信任,并且很难理解生产问题。我发现更麻烦的是对象契约的破裂,因为框架可能依赖它。
是否计划修改(如果不是实现的话),至少是javadoc以便更清楚地指定它?