1. 比较Java中的对象
在 Java 中,对象可以通过引用和值进行比较。
比较参考
如果两个变量在内存中指向同一个对象,那么这些变量中存储的引用是相等的。如果使用相等运算符 ( ==
) 比较这些变量,结果为真,并且该结果有意义。这里的一切都很简单。
代码 | 控制台输出 |
---|---|
|
|
按价值比较
但是您经常会遇到两个变量引用两个相同的不同对象的情况。例如,包含相同文本的两个不同字符串对象。
要确定不同的对象是否相同,请使用equals()
方法。例如:
代码 | 控制台输出 |
---|---|
|
|
该equals
方法不限于类String
。每个班级都有。
甚至是您自己编写的类,这就是原因。
2.Object
类
Java 中的所有类都继承该类Object
。Java 的创建者想出了这种方法。
而如果一个类继承了该类Object
,那么它就获得了该类的所有方法Object
。这是继承的主要结果。
换句话说,每个类都有Object
类的方法,即使他们的代码没有提到它们。
这些继承的方法包括与对象比较相关的方法。这些是equals()
和hashCode()
方法。
代码 | 实际上,这就是我们将拥有的: |
---|---|
|
|
在上面的示例中,我们创建了一个Person
带有 name 和 age 参数的简单类,但没有一个方法。但是因为所有的类都继承了这个Object
类,所以这个Person
类自动有两个方法:
方法 | 描述 |
---|---|
|
比较当前对象和传递的对象 |
|
返回当前对象的哈希码 |
事实证明,绝对每个对象都有equals
方法,不同类型的对象可以相互比较。这样的代码将编译并完美运行。
代码 | 控制台输出 |
---|---|
|
|
|
|
3.equals()
方法
该equals()
方法继承自该类Object
,实现了比较当前对象与传递的对象的最简单算法:它只是比较对对象的引用。
Person
如果您只是比较变量而不是调用方法,您会得到相同的结果equals()
。例子:
代码 | 控制台输出 |
---|---|
|
|
当equals
调用该方法时a
,它只是将存储在a
变量中的引用与存储在b
变量中的引用进行比较。
但是,类的比较工作不同String
。为什么?
因为创建该类的人String
编写了他们自己的方法实现equals()
。
equals()
方法的实现
现在让我们在类中编写我们自己的 equals 方法的实现Person
。我们将考虑 4 个主要案例。
equals
方法,它总是以一个Object
对象作为参数
场景 1:调用该方法的同一对象equals
也传递给该equals
方法。如果当前对象和传递对象的引用相等,则该方法必须返回true
。一个对象等于它自己。
在代码中它看起来像这样:
代码 | 描述 |
---|---|
|
比较参考资料 |
场景 2:null
传递给equals
方法——我们没有什么可比较的。调用该方法的对象equals
肯定不为null,所以我们需要false
在这种情况下返回。
在代码中它看起来像这样:
代码 | 描述 |
---|---|
|
比较引用 是传递的对象 null 吗? |
场景 3:将对不是 a 的对象的引用传递Person
给该equals
方法。对象是否Person
等于非Person
对象?这是一个由课程开发者Person
决定的问题,无论他或她想要什么。
但通常对象必须属于同一类才能被视为相等。因此,如果类的对象以外的东西Person
被传递给我们的 equals 方法,那么我们将始终返回false
。如何检查对象的类型?没错——通过使用instanceof
运算符。
这是我们的新代码的样子:
代码 | 描述 |
---|---|
|
比较引用 是传递的对象 null 吗? 如果传递的对象不是 Person |
4.比较两个Person
对象
我们最终得到了什么?如果我们已经到达方法的末尾,那么我们有一个Person
不是的对象引用null
。所以我们将其转换为a Person
,并比较两个对象的相关内部数据。这是我们的第四个场景。
代码 | 描述 |
---|---|
|
比较引用 是传递的对象 null 吗? 如果传递的对象不是 Person 类型转换 |
你如何比较两个Person
对象?如果他们具有相同的名字 ( name
) 和年龄 ( age
),则它们是相等的。最终代码将如下所示:
代码 | 描述 |
---|---|
|
比较引用 是传递的对象 null 吗? 如果传递的对象不是 Person 类型转换 |
但这还不是全部。
首先,name字段是a String
,所以需要通过调用equals
方法来比较name字段。
this.name.equals(person.name)
其次,该name
字段可能是null
:在这种情况下,您不能调用equals
它。您需要额外检查null
:
this.name != null && this.name.equals(person.name)
也就是说,如果名称字段null
在两个Person
对象中,那么名称仍然相同。
第四种情况的代码可能如下所示:
|
如果年龄不相等, 则立即 return false If this.name 等于null ,使用该方法进行比较没有意义equals 。这里要么第二个name 字段等于null ,要么不等于。 使用 方法比较两个名称字段 equals 。 |
5.hashCode()
方法
除了equals
旨在对两个对象的所有字段执行详细比较的方法之外,还有另一种方法可用于不精确但非常快速的比较:hashCode()
.
想象一下,您正在按字母顺序对包含数千个单词的列表进行排序,并且您需要反复比较单词对。单词很长,由很多字母组成。一般来说,这样的比较会耗费很长时间。
但它可以加速。假设我们有以不同字母开头的单词——很明显它们是不同的。但如果它们以相同的字母开头,那么我们还不能说结果会是什么:这些词可能相同或不同。
该hashCode()
方法使用类似的原理工作。如果你在一个对象上调用它,它会返回一些数字——类似于单词中的第一个字母。该数字具有以下属性:
- 相同的对象总是具有相同的哈希码
- 不同的对象可以有相同的哈希码,或者它们的哈希码可以不同
- 如果对象有不同的哈希码,那么对象肯定是不同的
为了更清楚地说明这一点,让我们用文字重新定义这些属性:
- 相同的单词总是有相同的首字母。
- 不同单词的首字母可以相同,也可以不同
- 如果单词的首字母不同,则单词肯定不同
最后一个属性用于加速对象比较:
首先,计算两个对象的哈希码。如果这些hashcode不一样,那么对象肯定是不一样的,没必要再去比较了。
但是如果哈希码相同,那么我们仍然需要使用 equals 方法来比较对象。
6. 代码契约
上述行为必须由 Java 中的所有类实现。在编译期间,无法检查对象是否被正确比较。
Java 程序员有一个普遍的共识,如果他们自己编写 equals() 方法的实现并因此覆盖标准实现(在类中),他们也必须按照上述规则Object
编写自己的方法实现hashCode()
使满意。
这种安排称为合同。
如果您只在您的班级中实施该方法equals()
或仅实施该hashCode()
方法,那么您就严重违反了合同(您违反了协议)。不要这样做。
如果其他程序员使用您的代码,它可能无法正常工作。此外,您将使用依赖于遵守上述合同的代码。
所有Java集合在查找元素时,都是先比较对象的hashcode,然后才使用方法进行比较equals
。
这意味着,如果您为自己的类提供了一个equals
方法,但您没有编写自己的hashCode()
方法或未正确实现它,那么集合可能无法与您的对象一起正常工作。
例如,您可能将一个对象添加到列表中,然后使用该方法搜索它contains()
,但集合可能找不到您的对象。
GO TO FULL VERSION