本文将为你解决以下几个核心问题
- == 和equals方法的区别从本质上去分析到底在哪里?(见CH2)
- 为什么总会出现要求equals方法重写,甚至在List中要求必须进行equals方法的重写?(见CH3)
在学习java过程中,很多初学者会被 == 运算 和.equals() 方法所迷惑,等到好不容易在String部分弄懂了两者的关系后,但在对象的对比和List中关于需要重写类的.equals()方法时又被搞晕。
今天一个初学者又再次问了阿冬哥这个问题,自己通过参考网络的文献等资料,这次尝试以最清楚简明的思路给大家彻底理清这块的思路。
说起equals方法,我们都知道是超类Object中的一个基本方法,用于检测一个对象是否与另外一个对象相等。而在Object类中这个方法实际上是判断两个对象是否具有相同的引用,如果有,它们就一定相等。其源码如下:
public boolean equals(Object obj) {
return (this == obj);
}
实际上我们知道所有的对象都拥有标识(内存地址)和状态(数据),同时“==”比较两个对象的的内存地址,所以说 Object 的 equals() 方法是比较两个对象的内存地址是否相等,即若 object1.equals(object2) 为 true,则表示 equals1 和 equals2 实际上是引用同一个对象。
class A{
String str1 = “ABC”;
String str2 = “ABC”;
}
这里的在方法区只有一块内存存储字符串"ABC",然后str1和str2同时指向那块内存的地址,所以,String的equals方法其本质还是.==去对比内存的地址,这也是本质,是核心,理解这句话,就是本文的目的之一所在。
或许这是我们面试时更容易碰到的问题”equals方法与‘= = ’ 运算符有什么区别?“,并且常常我们都会胸有成竹地回答:“equals比较的是对象的内容,而‘= =’比较的是对象的地址。”。但是从前面我们可以知道equals方法在Object中的实现也是间接使用了‘==’运算符进行比较的,所以从严格意义上来说,我们前面的回答并不完全正确。我们先来看一段代码并运行再来讨论这个问题。
package com.zejian.test;
public class Car {
private int batch;
public Car(int batch) {
this.batch = batch;
}
public static void main(String[] args) {
Car c1 = new Car(1);
Car c2 = new Car(1);
System.out.println(c1.equals(c2));
System.out.println(c1 == c2);
}
}
运行结果
false
false
分析:对于‘= =’运算符比较两个Car对象,返回了false,这点我们很容易明白,毕竟它们比较的是内存地址,而c1与c2是两个不同的对象,所以c1与c2的内存地址自然也不一样。现在的问题是,我们希望生产的两辆的批次(batch)相同的情况下就认为这两辆车相等【即以批次判断是否是相同的车】,但是运行的结果是尽管c1与c2的批次相同,但equals的结果却反回了false。当然对于equals返回了false,我们也是心知肚明的,因为equal来自Object超类,访问修饰符为public,而我们并没有重写equal方法,故调用的必然是Object超类的原始方equals方法,根据前面分析我们也知道该原始equal方法内部实现使用的是’=='运算符,所以返回了false。因此为了达到我们的期望值,我们必须重写Car的equal方法,让其比较的是对象的批次(即对象的内容),而不是比较内存地址,于是修改如下:
@Override
public boolean equals(Object obj) {
if (obj instanceof Car) { //这里只是判断是不是car类型,但此时obj作为Object类型引入,编译器还是仅限于知道它是一个Object对象,而不是Car类型的对象
Car c = (Car) obj; //在这里,因为传进来的时obj对象,所以你需要先if判断,然后强转,不然你下面无法获取batch属性的(Object可没有batch属性,所以无法调出),另外你重写equals方法的目的就是为了对比值得等量逻辑关系
return batch == c.batch;
}
return false;
}
使用instanceof来判断引用obj所指向的对象的类型,如果obj是Car类对象,就可以将其强制转为Car对象,然后比较两辆Car的批次,相等返回true,否则返回false。当然如果obj不是 Car对象,自然也得返回false。我们再次运行:
true
false
嗯,达到我们预期的结果了。因为前面的面试题我们应该这样回答更佳
总结:默认情况下也就是从超类Object继承而来的equals方法与‘==’是完全等价的,比较的都是对象的内存地址,但我们可以重写equals方法,使其按照我们的需求的方式进行比较,如String类重写了equals方法,使其比较的是字符的序列,而不再是内存地址。
@Override
public boolean equals(Object obj) {
if (obj instanceof BigCar) {
return true;
}
return false;
}
-先判断类型,在强制转换判断属性
@Override
public boolean equals(Object obj) {
if (obj instanceof Car) { //这里只是判断是不是car类型,但此时obj作为Object类型引入,编译器还是仅限于知道它是一个Object对象,而不是Car类型的对象
Car c = (Car) obj; //在这里,因为传进来的时obj对象,所以你需要先if判断,然后强转,不然你下面无法获取batch属性的(Object可没有batch属性,所以无法调出),另外你重写equals方法的目的就是为了对比值得等量逻辑关系
return batch == c.batch;
}
return false;
}