[英]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#L1013和https://github.com/openjdk-mirror/ jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/java/lang/String.java#L1494
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
对象,而上面的行比较了Ball
的color
字段引用的String
对象。
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
方法中, this
和Object 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.接下来我们可以检查您的代码。
equals
equals
的三个阶段The logic of your equals
is three phased:您的
equals
逻辑分为三个阶段:
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.equals
与Object#equals
混淆。
static
method on the utility class Objects
— notice the plural s
.Objects
上的static
方法——注意复数s
。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
isObject.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
& hashCode
和toString
。
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.