equals() 的所属以及内部原理(即 Object 中 equals 方法的实现原理)
public boolean equals(Object obj) {
return (this == obj);
}
实际上我们知道所有的对象都拥有标识(内存地址)和状态(数据),同时“==”比较两个对象的的内存地址,所以说 Object 的 equals() 方法是比较两个对象的内存地址是否相等,即若 object1.equals(object2) 为 true,则表示 equals1 和 equals2 实际上是引用同一个对象。
equals() 与 ‘==’ 的区别
总结:默认情况下也就是从超类 Object 继承而来的 equals 方法与 ‘==’ 是完全等价的,比较的都是对象的内存地址,但我们可以重写 equals 方法,使其按照我们的需求的方式进行比较,如 String 类重写了 equals 方法,使其比较的是字符的序列,而不再是内存地址。
equals()的重写规则
-
自反性。对于任何
非null
的引用值x
,x.equals(x)
应返回true
。 -
对称性。对于任何
非null
的引用值x
与y
,当且仅当:y.equals(x)
返回true
时,x.equals(y)
才返回true
。 -
传递性。对于任何
非null
的引用值x
、y
与z
,如果y.equals(x)
返回true
,y.equals(z)
返回true
,那么x.equals(z)
也应返回true
。 -
一致性。对于任何
非null
的引用值x
与y
,假设对象上equals
比较中的信息没有被修改,则多次调用x.equals(y)
始终返回true
或者始终返回false
。 -
对于任何非空引用值
x
,x.equal(null)
应返回false
。
当然在通常情况下,如果只是进行同一个类两个对象的相等比较,一般都可以满足以上5点要求。
有父类的时候需要注意覆写父类的 equals 方法:
@Override
public boolean equals(Object obj) {
if (obj instanceof BigCar) {
BigCar bc = (BigCar) obj;
return super.equals(bc) && count == bc.count;
}
return super.equals(obj);
}
为什么重写 equal() 时也得重写 hashCode()
这个问题主要是针对映射相关的操作(Map接口) Map接口的类会使用到键对象的哈希码,当我们调用put
方法或者get
方法对Map容器进行操作时,都是根据键对象的哈希码来计算存储位置的,因此如果我们对哈希码的获取没有相关保证,就可能会得不到预期的结果。
Objects.hashCode(o)
和 Objects.hash(...o)
Java 7 中对hashCode
方法做了两个改进,首先java发布者希望我们使用更加安全的调用方式来返回散列码,也就是使用null
安全的方法 Objects.hashCode
(注意不是Object而是java.util.Objects) 方法,这个方法的优点是如果参数为null
,就只返回0
,否则返回对象参数调用的hashCode
的结果。
Objects.hashCode
源码如下:
public static int hashCode(Object o) {
return o != null ? o.hashCode() : 0;
}
Java 7 还提供了另外一个方法 java.util.Objects.hash(Object... objects)
, 当我们需要组合多个散列值时可以调用该方法。
@Override
public int hashCode() {
return Objects.hash(name,salary,sex);
}
在 Java API 文档中关于 hashCode 方法有以下几点规定(原文来自java深入解析一书)。
-
在java应用程序执行期间,如果在
equals
方法比较中所用的信息没有被修改,那么在同一个对象上多次调用hashCode
方法时必须一致地返回相同的整数。如果多次执行同一个应用时,不要求该整数必须相同。 -
如果两个对象通过调用
equals
方法是相等的,那么这两个对象调用hashCode
方法必须返回相同的整数。 -
如果两个对象通过调用
equals
方法是不相等的,不要求这两个对象调用hashCode
方法必须返回不同的整数。 但是程序员应该意识到对不同的对象产生不同的hash值可以提供哈希表的性能。
编写一个完美equals()的几点建议
(出自Java核心技术 第一卷:基础知识)
-
(1)显式参数命名为
otherObject
, 稍后需要将它转换成另一个叫做other
的变量 -
(2)检测
this
与otherObject
是否引用同一个对象 :if(this == otherObject) return true
;(存储地址相同,肯定是同个对象,直接返回true) -
(3) 检测
otherObject
是否为null
,如果为null
,返回false
:if(otherObject == null) return false
; -
(4) 比较
this
与otherObject
是否属于同一个类 (视需求而选择)-
如果equals的语义在每个子类中有所改变,就使用
getClass
检测 :if(getClass()!=otherObject.getClass()) return false
-
如果所有的子类都拥有统一的语义,就使用
instanceof
检测 :if(!(otherObject instanceof ClassName)) return false
-
-
(5) 将
otherObject
转换为相应的类类型变量:ClassName other = (ClassName) otherObject
; -
(6) 现在开始对所有需要比较的域进行比较 。使用 == 比较基本类型域,使用 equals 比较对象域。如果所有的域都匹配,就返回
true
,否则就返回false
。
如果在子类中重新定义 equals
,就要在其中包含调用super.equals(other)
当此方法被重写时,通常有必要重写 hashCode
方法,以维护 hashCode
方法的常规协定: 相等对象必须具有相等的哈希码 。
关于 list.contains(o)
方法: 其实内部是for循环拿 o.equals(a[i])
去比较的
@Override
public boolean contains(Object o) {
return indexOf(o) != -1;
}
@Override
public int indexOf(Object o) {
E[] a = this.a;
// 当o是null的时候也会返回集合中null的位置
if (o == null) {
for (int i = 0; i < a.length; i++)
if (a[i] == null)
return i;
} else {
// indexOf(),实际调用的是传入的contains(Object o)的实参的equals方法
for (int i = 0; i < a.length; i++)
if (o.equals(a[i]))
return i;
}
return -1;
}
参考:https://blog.youkuaiyun.com/javazejian/article/details/51348320