简体   繁体   English

equals 和 hashCode 如何在幕后工作?

[英]How do equals and hashCode work under the hood?

I researched this question and the answers I got do not satisfy me as they don't explain these things deeply enough.我研究了这个问题,我得到的答案并不让我满意,因为他们没有足够深入地解释这些事情。 So, it is known that for HashSet with a parametrized custom class it is necessary to override hashCode and equals in order to forbid duplicates.因此,众所周知,对于具有参数化自定义类的 HashSet,必须覆盖 hashCode 和 equals 以禁止重复。 But in practice when I tried to understand how this really works I didn't quite get it.但在实践中,当我试图了解它是如何真正运作时,我并没有完全理解。 I have a class:我有一堂课:

static class Ball {
    String color;

    public Ball(String color) {
        this.color = color;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Ball ball = (Ball) o;
        return Objects.equals(color, ball.color);
    }

    @Override
    public int hashCode() {
        return Objects.hash(color);
    }
    }

In equals method, it is all clear.在equals方法中,一切都清楚了。 If two 'variables' are pointing to the same object in memory, then they are equal;如果两个“变量”指向内存中的同一个对象,那么它们是相等的; if an o is null or they are not of the same class - they are not equal.如果 o 为 null 或者它们不属于同一类 - 它们不相等。 The last line of equals is what's concerning me.等号的最后一行是关于我的。 When I go to the Objects.equals :当我去 Objects.equals :

public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}

It says once again if two 'variables' refer the same object, then they are equal or if the first object is not null and it is equal to the second one.它再次表示,如果两个“变量”引用同一个对象,那么它们相等,或者如果第一个对象不为 null 并且它等于第二个对象。 This equals is Object.equals which will return true if only these two objects aka 'variables' are pointing to the same object in memory.这等于是 Object.equals 如果只有这两个对象(即“变量”)指向内存中的同一个对象,它将返回 true。 So, how does this really works?那么,这到底是如何工作的呢? Been looking for a clear answer, but as I said, what I've got so far does not satisfy me at all.一直在寻找一个明确的答案,但正如我所说,到目前为止我所得到的根本不能满足我。

In your class, you explained it very well.在你的课上,你解释得很好。

The part that you are missing is that at some point, your code will delegate to the equals and hashCode on the color attribute, which is implemented by the java.lang.String class.您缺少的部分是,在某些时候,您的代码将委托给color属性上的 equals 和 hashCode,这是由java.lang.String类实现的。

See eg https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/java/lang/String.java#L1013 and https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/java/lang/String.java#L1494参见例如https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/java/lang/String.java#L1013https://github.com/openjdk-mirror/ jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/java/lang/String.java#L1494

tl;dr tl;博士

return Objects.equals(color, ball.color);返回 Objects.equals(color, ball.color);

You are passing two String objects here, not Ball objects.您在这里传递了两个String对象,而不是Ball对象。

Your override of Object#equals compares Ball objects, while the line above compares the String objects referenced by the color field of Ball .您对Object#equals的覆盖比较了Ball对象,而上面的行比较了Ballcolor字段引用的String对象。

Details细节

First, let's clarify the terminology.首先,让我们澄清一下术语。

This equals is Object.equals which will return true if only these two objects aka 'variables' are pointing to the same object in memory.这等于是 Object.equals 如果只有这两个对象(即“变量”)指向内存中的同一个对象,它将返回 true。

In your equals method, both this and Object o are reference variables, not objects.在您的equals方法中, thisObject o都是引用变量,而不是对象。 Either variable can hold no reference at all (null) or either variable can hold a reference (pointer) to an object living elsewhere in memory.任何一个变量都不能持有任何引用(null),或者任何一个变量都可以持有指向内存中其他地方的对象的引用(指针)。

Next we can examine your code.接下来我们可以检查您的代码。

Three Phases of equals equals的三个阶段

The logic of your equals is three phased:您的equals逻辑分为三个阶段:

  • Identity check身份检查
  • Null check空检查
  • Content comparison内容比较

The first phase performs an identity check .第一阶段执行身份检查 We look to see if both reference variables refer to the same object, the same chunk of memory.我们查看两个引用变量是否引用同一个对象,同一个内存块。 If so, there is no need for further consideration: An object is always equal to itself.如果是这样,则无需进一步考虑:一个对象总是等于它自己。 So return true , job done.所以返回true ,工作完成。

The second phase performs a null check .第二阶段执行空值检查 If either of the two objects being compared is null, we report false , meaning “not equal”.如果要比较的两个对象中的任何一个为空,我们报告false ,意思是“不相等”。 To perform the null check, we skip this .要执行空值检查,我们跳过this The this reference variable cannot be null by definition.根据定义, this引用变量不能为空。 We move on to the Object o .我们继续讨论Object o If null, report false , job done.如果为 null,则报告false ,工作完成。

The third phase compares content .第三阶段比较内容 We examine the content of the object referenced by this .我们检查this引用的对象的内容。 And we examine the content of the object referenced by Object o .我们检查Object o引用的对象的内容。 We know we have two separate objects (two separate chunks of memory) because at this point in the code we got past the identity check.我们知道我们有两个独立的对象(两个独立的内存块),因为此时在代码中我们通过了身份检查。

In your case with the Ball class, you chose to compare the one and only piece of state, the member field color .在您使用Ball类的情况下,您选择比较唯一的状态,即成员字段color That member field holds a reference to a String object.该成员字段包含对String对象的引用。 So, after casting o to Ball ball , we compare the string from this.color to the string from ball.color .因此,在将o转换为Ball ball之后,我们将 this.color 中的字符串与this.color中的字符串进行ball.color

This seems to be the sticking point in your understanding.这似乎是你理解的症结所在。 👉 The casting is crucial here. 👉 选角在这里至关重要。 After successfully casting from Object o to Ball ball , the Java Virtual Machine at runtime knows that the object in question is indeed a Ball (or a subclass of Ball ).在从Object o成功转换为Ball ball之后,Java 虚拟机在运行时知道所讨论的对象确实是Ball (或Ball的子类)。 As a Ball , we have access to its color field.作为一个Ball ,我们可以访问它的color域。

In the call to Objects.equals(color, ball.color) we are comparing two String objects, not two Ball objects.在对Objects.equals(color, ball.color)的调用中,我们比较的是两个String对象,而不是两个Ball对象。 You may find clarity in expanding that code.您可能会在扩展该代码时发现清晰。

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Ball ball = (Ball) o;
    String thisColor = this.color ; 
    String thatColor = ball.color ; 
    boolean colorsAreTheSame = thisColor.equals( thatColor );
    return colorsAreTheSame ; 
}

You said:你说:

When I go to the Objects.equals :当我去 Objects.equals :

Your code is passing two String objects to Objects.equal , not two Ball objects.您的代码将两个String对象传递给Objects.equal ,而不是两个Ball对象。

By the way, do not confuse Objects.equals with Object#equals .顺便说一句,不要将Objects.equalsObject#equals混淆。

  • This first is a static method on the utility class Objects — notice the plural s .这首先是实用程序类Objects上的static方法——注意复数s
  • The second is an instance method defined on the ultimate superclass Object — note the singular, with no s on the end.第二个是在最终超类Object上定义的实例方法——注意单数,末尾没有s The second is inherited by Ball , but then overridden by the Ball class' own implementation.第二个由Ball继承,但随后被Ball类自己的实现覆盖。 So the implementation provided by Object#equals is never used in your scenario.因此,您的场景中永远不会使用Object#equals提供的实现

You said:你说:

This equals is Object.equals which will return true if only these two objects aka 'variables' are pointing to the same object in memory.这等Object.equals equals只有这两个对象(即“变量”)指向内存中的同一个对象,它将返回 true。

You cut it short there.你在那里剪短了。 The Object.equals method performs the same three phased logic as your equals method: firstly identity check, secondly null check, and thirdly whatever is implemented in the equals method of the two objects. Object.equals方法执行与您的equals方法相同的三阶段逻辑:首先是身份检查,其次是 null 检查,最后是在两个对象的equals方法中实现的任何内容。

And most importantly, you are passing two String objects to Objects.equals , whereas your override of equals compares two Ball objects.最重要的是,您将两个String对象传递给Objects.equals ,而您对equals的覆盖比较两个Ball对象。


By the way, if the purpose of your class is to communicate date transparently and immutably, you can more briefly define the class as a record in Java 16+.顺便说一句,如果您的类的目的是透明且不可变地传达日期,您可以更简单地将类定义为 Java 16+ 中的记录

In a record, you merely declare the type and name of each member field.在一条记录中,您只需声明每个成员字段的类型和名称。 The compiler implicitly creates the constructor, getters, equals & hashCode , and toString .编译器隐式创建构造函数、getter、 equals & hashCodetoString

The default for this methods is to utilize each and every member field.此方法的默认设置是利用每个成员字段。 You can override equals & hashCode you want to consider a subset of the member fields.您可以覆盖您想要考虑成员字段子集的equals & hashCode

Here is your entire Ball class when written as a record.这是作为记录编写的整个Ball课程。

record Ball ( String color ) {}

Usage:用法:

Ball redBall = new Ball( "red" ) ;
Ball blueBall = new Ball( "blue" ) ;
boolean sameBall = redBall.equals( blueBall ) ;  // false

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

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