HashSet的“add”方法何时调用equals?

11

我在 HashSet 比较中进行了这个测试,equals 没有被调用。

当 farAway=false 时,我希望考虑 equals。(这是用于检查两个点之间距离的函数)

这是一份完整可编译的代码,你可以测试它,并告诉我为什么在这个例子中 equals 没有被调用。

public class TestClass{
     static class Posicion
    {
        private int x;
        private int y;

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final Posicion other = (Posicion) obj;
            if ( farAway(this.x, other.x, this.y, other.y,5)){   
                return false;
            } 
            return true;
        }

        @Override
        public int hashCode() {
            int hash = 7; hash = 59 * hash + this.x; hash = 59 * hash + this.y;
            return hash;
        }

         Posicion(int x0, int y0) {
            x=x0;
            y=y0;
        }

        private boolean farAway(int x, int x0, int y, int y0, int i) {
            return false;
        }
    }

    public static void main(String[] args) {
        HashSet<Posicion> test=new HashSet<>();
        System.out.println("result:"+test.add(new Posicion(1,1)));
        System.out.println("result:"+test.add(new Posicion(1,2)));
    }
}

编辑

-有没有一种方法可以强制 HashSet add 方法调用 equals 方法?


equals 内的 farAway 调用是什么意思?你的类破坏了 hashCodeequals 之间的约定! - Alonso Dominguez
@AlonsoDominguez,这里有一个完整的测试,farAway只返回false,以确保对象被视为相等,但从未调用equals方法。 - Hernán Eche
你必须遵守hashCodeequals之间的契约。这意味着当你的equals方法返回true时,你的hashCode方法必须返回相同的整数值。但是在你的代码中并没有发生这种情况。 - Alonso Dominguez
@AlonsoDominguez 我明白了,我试图避免使用hashCode,只使用equals,现在我知道这是不可能的,感谢您坚持使用“协定”破坏,现在我知道当equals没有被调用时,我仍然不知道什么时候会调用equals。 - Hernán Eche
1
@HernánEche:你似乎想做的事情可能非常棘手 - 你不仅需要遵守hashCode()的契约,还要遵守equals()的契约。equals()必须是传递性的。因此,如果您因为A和B之间很接近而返回true,因为它们很接近,B和C之间也很接近,那么当检查A和C之间的相等性时,如果它们恰好不接近,则会遇到问题。我认为你需要设计一个容器/数据结构,而不是标准的HashSet来实现你想要做的事情(如果你想要做的事情在第一时间就是一个好主意的话)。 - Michael Burr
@MichaelBurr 精辟的见解+1(还有有趣的括号),好观点,欧几里得距离不会是传递的,设置任何阈值,A可能会“接近”B,B“接近”C,但我们不能说关于C相对于A的任何事情,它可能接近也可能不接近,那么“equals”应该与所有元素进行比较(它没有这样做)。我意识到了这一点,它没有破坏我的代码,因为这里有一个加载顺序,并且它不是欧几里得距离,但这是一个非常有用的评论,谢谢。 - Hernán Eche
4个回答

32
如果哈希码不同,则无需调用 equals(),因为它保证返回 false
这是基于 equals()hashCode() 的一般contract得出的结论:
如果两个对象根据 equals(Object) 方法是相等的,则在每个对象上调用 hashCode 方法必须产生相同的整数结果。
现在你的类正在违反这个合同。你需要修复它。

这是关于hashCode不同而equals没有被调用的问题。 - Hernán Eche
4
@HernánEche: 这没有意义。HashSet 调用 equals() 方法来确保不会将重复对象存储在集合中。如果它可以在不调用 equals() 的情况下确保这一点 (例如,因为集合中还没有任何内容,或者因为集合中没有其他对象具有相同的哈希码),那么它为什么要调用 equals()?也不知道要与哪个其他对象比较。 - JB Nizet
1
@HernánEche:这两个对象返回不同的哈希码。根据hashCode()的约定,库(或任何人)可以认为这意味着这些对象不相等 - 如果哈希码不同,就没有必要调用equals() - Michael Burr
@MichaelBurr 的意思是,如果哈希码不同,则无需调用 equals,如果哈希码相等,则无需调用 equals,最终 equals 永远不会被调用,因此这个问题的答案应该是,“无论您在 equals 中或 hashCode 中放入什么内容,equals 都不会被调用”。 - Hernán Eche
7
如果哈希码不同,那么对象就不相同,equals()方法也不会被调用。如果哈希码相同,对象可能相等,因此会调用equals(Object)方法来确保它们是相同的。 - jarnbjo
显示剩余6条评论

13
如果你希望equals()方法总是被调用,只需要在hashCode()方法中始终返回相同的值,例如0。这样所有的项目将具有相同的哈希码,只会使用equals()方法进行比较。
public int hashCode() {
  return 0;
}

那么您能解释一下您期望的结果是什么吗?您的代码应该产生什么输出? - Sebastian Krysmanski
功能是拒绝添加两个相似的对象,例如平面上彼此接近的两个点,但我可以用数百种方式实现相同的功能,问题不在于解决单个问题,而在于理解HashMap何时实际调用equals方法,或者是否根本不通过它调用equals方法。 - Hernán Eche
我刚刚测试了我的代码,它按预期工作。调用test.add(new Posicion(1,2)返回false,因为调用了equals()。这有什么问题吗? - Sebastian Krysmanski
我的测试出了问题,现在已经全部解决了,谢谢。 - Hernán Eche
使用这个技巧有什么缺点吗? - optimusfrenk
显示剩余2条评论

1

听起来 HashSet 不适合你。 看起来你想要一种自定义的比较两个位置的方式。而不是说“两个位置完全相等吗?”。 相反,你应该考虑使用 TreeSet,并使用 Comparator。这样,你可以编写一个“IsWithinRangeComparator”,并在那里进行范围检查。


-1
如上所述,当对象相等时,它们的哈希码也应该相同。您可以像下面这样对哈希码进行简单修复。
 public int hashCode() {

int hash = 7; hash = 59 * hash + this.x; hash = 59 * hash + this.y;
boolean faraway=farAway(this.x, other.x, this.y, other.y,5);
hash=59*hash+(faraway?1:0); //include faraway also as part of hashcode computation

 return hash;

}


1
当然不可能从hashCode()方法中使用farAway方法,因为你没有其他对象可以进行比较。 - jarnbjo
1
@herman:哈希表通过比较对象的哈希码来工作。如果哈希码不同,则无需调用equals(),因为对象被认为是不同的,并且两个对象都将放入哈希表中。仅当哈希码相同时才会调用equals(),在这种情况下,将调用equals()来比较值。 - Zenil
@Zenil 不会,如果哈希码相同,equals方法不会被调用,因为这意味着对象是相同的!那么我仍然想知道,什么时候会调用equals方法? - Hernán Eche
@herman,@jarnbbjo:faraway()需要5个参数。为什么你需要另一个对象进行比较?你需要传入的其他对象的x和y。faraway()可以在hashcode中调用,就像在equals()中调用一样。我有什么遗漏吗? - Zenil
1
@Zenil:是的,你漏掉了一些东西。如果你没有其他对象,你想从哪里获取参数other.x和other.y呢? - jarnbjo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接