JVM 默认 Object.HashCode() 实现背后的原因

Reason behind JVM's default Object.HashCode() implementation

我试图理解为什么 JVM 的默认实现对所有对象 return 没有相同的 hashcode() 值...

我写了一个程序,我覆盖了 equals() 但没有覆盖 hashCode(),结果很可怕。

  1. HashSet 将两个对象相加,即使它们相等。
  2. TreeSet Comparable 实现抛出异常..

还有很多..

如果默认 Object'shashCode() 实现 return 具有相同的 int 值,所有这些问题都可以避免...

我理解他们写了很多关于 hashcode() and equals() 的讨论,但我无法理解为什么默认情况下不能处理事情,这很容易出错,后果可能非常糟糕和可怕..

这是我的示例程序..

import java.util.HashSet;
import java.util.Set;


public class HashcodeTest {

    public static void main(String...strings ) {

        Car car1 = new Car("honda", "red");
        Car car2 = new Car("honda", "red");
        Set<Car> set = new HashSet<Car>();

        set.add(car1);
        set.add(car2);
        System.out.println("size of Set : "+set.size());
        System.out.println("hashCode for car1 : "+car1.hashCode());
        System.out.println("hashCode for car2 : "+car2.hashCode());

    }

}

class Car{
    private String name;
    private String color;



    public Car(String name, String color) {
        super();
        this.name = name;
        this.color = color;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }


    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Car other = (Car) obj;
        if (color == null) {
            if (other.color != null)
                return false;
        } else if (!color.equals(other.color))
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

}

Output:

size of Set : 2

car1 的哈希码:330932989

hashCode for car2 : 8100393

你违约了。

hashcode 和 equals 应该这样写,当等于 return true 时,这些对象具有相同的 hashcode。

如果您重写 equals,那么您必须提供可以正常工作的哈希码。

默认实现无法处理它,因为默认实现不知道哪些字段是重要的。而且自动实现效率不高,hashcode函数是为了加快数据结构中的数据查找等操作,如果实现不当,性能会受到影响。

来自Docs

As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the JavaTM programming language.)

来自文档:

If two objects are equal according to the equals(Object) method, then calling the hashCode} method on each of the two objects must produce the same integer result.

那么如果您重写 equals() 的行为方式,您也必须重写 hashCode()。

此外,来自 equals() 的文档 -

Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.

来自 Object class 的 javadoc:

Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by HashMap.

因此,如果默认实现提供相同的哈希值,它就达不到目的了。

并且对于默认实现,它不能假定所有 classes 都具有值 class,因此来自文档的最后一句话:

As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects.

您似乎想提议默认计算 hashCode,只需获取所有对象字段并使用某种公式组合它们的哈希码即可。这种做法是错误的,可能会导致许多不愉快的情况。在您的情况下它会起作用,因为您的对象非常简单。但现实生活中的物体要复杂得多。几个例子:

  • 对象连接成双链表(每个对象都有previousnext字段)。默认实现如何计算哈希码?如果它应该检查字段,它将以无限递归结束。

  • 好的,假设我们可以检测无限递归。让我们只使用单链表。在这种情况下,每个节点的hashCode应该从所有后继节点计算出来?如果这个列表包含数百万个节点怎么办?应该检查所有这些以生成 hashCode?

  • 假设您有两个 HashSet 对象。首先创建如下:

    HashSet<Integer> a = new HashSet<>();
    a.add(1);
    

    第二个是这样创建的:

    HashSet<Integer> b = new HashSet<>();
    for(int i=1; i<1000; i++) b.add(i);
    for(int i=2; i<1000; i++) b.remove(i);
    

    从用户的角度来看,两者都只包含一个元素。但是以编程方式,第二个在内部包含大哈希-table(就像 2048 个条目的数组,其中只有一个不为空),因为当您添加许多元素时,哈希-table 被调整大小。相反,第一个在内部包含小散列-table(例如 16 个元素)。所以编程对象非常不同:一个有大数组,另一个有小数组。但由于 hashCodeequals.

  • 的自定义实现,它们是相等的并且具有相同的 hashCode
  • 假设您有不同的 List 实现。例如,ArrayListLinkedList。两者都包含相同的元素,从用户的角度来看,它们是相等的,应该具有相同的 hashCode。而且它们确实相等并且具有相同的 hashCode。然而它们的内部结构完全不同:ArrayList 包含一个数组,而 LinkedList 包含指向表示头和尾的对象的指针。所以你不能只根据他们的字段生成哈希码:它肯定会有所不同。

  • 某些对象可能包含延迟初始化的字段(仅在必要时初始化为 null 并从其他字段计算)。如果您有两个其他方面相同的对象,并且一个已初始化其惰性字段而另一个未初始化怎么办?我们应该从 hashCode 计算中排除这个惰性字段。

因此,在很多情况下,通用 hashCode 方法不起作用,甚至可能会产生问题(例如使您的程序因 WhosebugError 而崩溃或在枚举所有链接对象时卡住)。因此,选择了基于对象身份的最简单的实现。请注意,hashCodeequals 的约定要求它们保持一致,并且默认实现已实现。如果您重新定义 equals,您也必须重新定义 hashCode