简体   繁体   English

hashCode()只应使用equals()中使用的不可变字段的子集吗?

[英]Should hashCode() only use the subset of immutable fields of those used in equals()?

Situation 情况

I needed to overwrite equals() and as it is recommended I also overwrote the hashCode() method using the same fields. 我需要覆盖equals() ,因为我建议我也使用相同的字段覆盖hashCode()方法。 Then, when I was looking at a set, that contained only the one object I got the frustrating result of 然后,当我看着一个只包含一个对象的集合时,我得到了令人沮丧的结果

set.contains(object)
=> false

while

set.stream().findFirst().get().equals(object)
=> true

I understand now, that this is due to changes that were made to object after it was added to set which again changed its hashCode. 我现在明白了,这是因为在将object添加到set之后对object所做的更改再次更改了其hashCode。 contains then looks at the wrong key and can't find the object . 然后contains查找错误的键并找不到该object

My requirements for the implementation are 我对实施的要求是

  • mutable fields are needed to correctly implement equals() 需要可变字段才能正确实现equals()
  • use these objects safely in hash-based Collections or Maps such ash HashSet even if they are prone to changes. 即使它们容易发生变化,也可以在基于散列的CollectionsMaps安全地使用这些对象,例如灰烬HashSet

which conflicts with the convention that 这违反了惯例

Question

Are there any dangers to using only a subset of fields which are used in equals() to calculate hashCode() instead of using all? 仅使用equals()中使用的字段子集计算hashCode()而不是全部使用是否存在任何危险?

More specifically this would mean: equals() uses a number of fields of the object whereas hashCode() only uses those fields that are used in equals() and that are immutable . 更具体而言,这将意味着: equals()使用了编号的对象,而字段的hashCode()只使用了在用于这些领域equals()和是不可变的

I think this should be okay, because 我认为这应该没问题,因为

  • the contract is fullfilled: equal objects will produce the same hashCode, while the same hashCode does not necesairly mean that the objects are the same. 合同是满的:相等的对象将产生相同的hashCode,而相同的hashCode并不意味着对象是相同的。
  • The hashCode of an object stays the same, even if an object is exposed to changes and therefore will be found in a HashSet before and after those changes. 即使对象暴露于更改,对象的hashCode也保持不变,因此可以在这些更改之前和之后的HashSet找到。

Related posts that helped me understand my problem but not how to solve it: What issues should be considered when overriding equals and hashCode in Java? 相关帖子帮助我理解了我的问题,但没有解决方法: 在Java中覆盖equals和hashCode时应该考虑哪些问题? and Different fields for equals and hashcode equals和hashcode的不同字段

The contract would indeed be fulfilled. 合同确实会实现。 The contract imposes that .equal() objects have ALWAYS the same .hashCode() . 合同强制.equal()对象始终具有相同的.hashCode() The opposite doesn't have to be true and I wonder with the obsession of some people and IDEs to apply exactly that practice. 相反的情况不一定是真的,我想知道一些人和IDE的痴迷恰好适用于这种做法。 If this was possible for all possible combinations, then you would discover the perfect hash function. 如果所有可能的组合都可以,那么你会发现完美的哈希函数。

BTW, IntelliJ offers a nice wizard when generating hashCode and equals by treating those two methods separately and allowing to differentiate your selection. 顺便说一句,IntelliJ在生成hashCode时提供了一个很好的向导,并且通过分别处理这两个方法并允许区分您的选择来提供相同的向导。 Obviously, the opposite, aka offering more fields in the hashCode() and less fields in the equals() would violate the contract. 显然,相反,又名提供在更多领域hashCode()和更少的领域中equals()违反约定。

For HashSet and similar collections/maps, it's a valid solution to have hashCode() use only a subset of the fields from the equals() method. 对于HashSet和类似的集合/映射,使用hashCode()只使用equals()方法中的字段子集是一种有效的解决方案。 Of course, you have to think about how useful the hash code is to reduce collisions in the map. 当然,您必须考虑哈希码有效减少地图中的冲突。

But be aware that the problem comes back if you want to use ordered collections like TreeSet . 但请注意,如果要使用TreeSet等有序集合,问题就会出现。 Then you need a comparator that never gives collisions (returns zero) for "different" objects, meaning that the set can only contain one of the colliding elements. 然后你需要一个永远不会给“不同”对象发生冲突(返回零)的比较器,这意味着该集合只能包含一个碰撞元素。 Your equals() description implies that multiple objects will exist that differ only in the mutable fields, and then you lose: 你的equals()描述意味着存在多个只在可变字段中不同的对象,然后你输了:

  • Including the mutable fields in the compareTo() method can change the comparison sign, so that the object needs to move to a different branch in the tree. 在compareTo()方法中包含可变字段可以更改比较符号,以便对象需要移动到树中的不同分支。
  • Excluding the mutable fields in the compareTo() method limits you to have maximum one colliding element in the TreeSet. 排除compareTo()方法中的可变字段会限制您在TreeSet中拥有最多一个碰撞元素。

So I'd strongly recommend to think about your object class'es concept of equality and mutability again. 所以我强烈建议再次考虑你的对象类的平等和可变性的概念。

That's perfectly valid to me. 这对我来说完全有效。 Suppose you have a Person : 假设你有一个Person

 final int name; // used in hashcode
 int income; // name + income used in equals

name decides where the entry will go (think HashMap ) or which bucket will be chosen. name决定条目的位置(想想HashMap )或选择哪个桶。

You put a Person as a Key inside HashMap : according to hashcode it goes to some bucket, second for example. 你把一个Person作为Key放在HashMap :根据hashcode它会进入某个桶,例如第二个。 You upgrade the income and search for that Person in the map. 您升级income并在地图中搜索该Person According to hashcode it must be in the second bucket, but according to equals it's not there: 根据hashcode它必须在第二个桶中,但根据equals它不存在:

 static class Person {
    private final String name;

    private int income;

    public Person(String name) {
        super();
        this.name = name;
    }

    public int getIncome() {
        return income;
    }

    public void setIncome(int income) {
        this.income = income;
    }

    public String getName() {
        return name;
    }

    @Override
    public int hashCode() {
        return name.hashCode();
    }

    @Override
    public boolean equals(Object other) {
        Person right = (Person) other;

        return getIncome() == right.getIncome() && getName().equals(right.getName());
    }

}

And a test: 并测试:

    HashSet<Person> set = new HashSet<>();
    Person bob = new Person("bob");
    bob.setIncome(100);
    set.add(bob);

    Person sameBob = new Person("bob");
    sameBob.setIncome(200);

    System.out.println(set.contains(sameBob)); // false

What you are missing I think is the fact that hashcode decides a bucket where an entry goes (there could be many entries in that bucket) and that's the first step, but equals decides if that is well, an equal entry. 您认为缺少的是hashcode决定一个条目所在的桶(该桶中可能有许多条目),这是第一步,但是equals决定它是否合适,是一个相等的条目。

The example that you provide is perfectly legal; 您提供的示例完全合法; but the one you linked is the other way around - it uses more fields in hashcode making it thus incorrect. 但你链接的那个是另一种方式 - 它在hashcode使用更多的字段,因此它是错误的。

If you understand these details that first hashcode is used to understand where and Entry might reside and only later all of them (from the subset or bucket) are tried to be found via equal - your example would make sense. 如果您了解这些细节,那么第一个哈希码用于了解Where和Entry 可能驻留的位置,并且只有稍后所有这些(来自子集或存储桶)都试图通过equal方式找到 - 您的示例才有意义。

It's ok for hashCode() to use a subset of the fields that equals() uses, although it may possibly give you a slight performance drop. hashCode()可以使用equals()使用的字段的子集,尽管它可能会给你一点性能下降。

Your problem seems to be caused by modifying the object, while still inside the set, in a way that alters the functioning of hashCode() and/or equals() . 您的问题似乎是由于修改了对象,同时仍然在集合内部,以改变hashCode()和/或equals() Whenever you add an object to a HashSet (or as the key in a HashMap), you must not subsequently modify any fields of that object that are used by equals() and/or hashCode() . 无论何时将对象添加到HashSet(或作为HashMap中的键),都不得随后修改equals()和/或hashCode()使用的该对象的任何字段。 Ideally, all fields used by equals() should be final . 理想情况下, equals()使用的所有字段都应该是final If they can't be, you must treat them as though they are final whilst the object is in the set. 如果它们不可能,则必须将它们视为最终,同时对象在集合中。

The same goes for TreeSet/TreeMap, too, but applies to fields used by compareTo() . TreeSet / TreeMap也是如此,但适用于compareTo()使用的字段。

If you really need to modify the fields that are used by equals() (or by compareTo() in the case of a TreeSet/TreeMap), you must: 如果您确实需要修改equals()使用的字段(或者在TreeSet / TreeMap的情况下通过compareTo() ),您必须:

  1. First, remove that object from the set; 首先,从集合中删除该对象;
  2. Then modify the object; 然后修改对象;
  3. And finally add it back to the set. 最后将它添加回集合中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM