Object类中有一个equals方法,该方法是用来检测两个对象是否相等,在Object类中,该方法的实现是对比两个对象的引用是否相等,而非对比内容是否相等,具体的实现如下:
public boolean equals(Object obj) {
return (this == obj);
}
一个简单的case,来说明Object类中的equals方法是比较的引用还是内容,具体代码如下所示:
public class TestEquals extends Object{
public static void main(String[] args) {
TestEquals t1 = new TestEquals();
TestEquals t2 = new TestEquals();
System.out.println(t1.equals(t2));
System.out.println(t1 == t2);
}}
运行结果如下所示:
false
false
我们创建了两个TestEquals对象,它们的内存地址不同,所以==的结果为false,但是为什么equals的内容也为空,按照道理t2和t1都应该为空对象,equals的比较结果应该为true才对,此时我们可以看看Object类中的equals方法实现,它对比的两个引用的地址是否相同,如果引用的地址在内存中是相同的,那么比较的结果才会返回true。
我们再来看一个例子,具体代码如下:
public class TestEquals extends Object{
public static void main(String[] args) {
TestEquals t1 = new TestEquals();
TestEquals t2 = new TestEquals();
System.out.println(t1.equals(t2));
System.out.println(t1 == t2);
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println(str1.equals(str2));
System.out.println(str1 == str2);
}
}
运行结果如右所示:false false true false
从结果我们可以看出来,str1.equals(str2)
返回值为true,str1==str2
返回值为false,原因很简单,因为String类重写了Object类的equals方法,所以它的equals对比的是内容,而非对比的引用的地址。后者是因为两个对象所new出来的内存地址不同,所以返回false。String类的equals实现如下:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
从上述代码可以看出,里面有两个if判断,第一个是判断是否内存地址一样,第二个判断对象类型是否为String类型,此处用了类型检查符instanceof来检查对象是否为String类型,接着将两个String对象转为字符数组,然后循环对比字符数组的每个元素,如果有一个元素不等则立马返回false,equals方法也因此结束,只有当两个对象的所有元素都相等时,才会返回true。
因此,我们可以看出,当该类是默认继承Object类的equals方法时,它的equals和==都是用来比较对象的内存地址的,如果内存地址相同,则返回true;反之返回false。如果该类需要使用equals比较内容,那么我们需要重写一下equals方法,比如String类,它就重写了Object类的equals方法,这样才可以比较两个对象的内容是否equals。
上面提到了如果一个类继承了Object类的equals方法,当我们需要使用equals时,我们需要重写一下equals方法,那么我们该如何重写equals方法呢?
关于equals方法的重写规则,我们可以从oracle官网看到相应的英文解释:
oracle官网对Object类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。
package com.lee.java;
import java.util.Iterator;
public class Dog {
private String name;
public Dog(String name) {
this.name = name;
}
public static void main(String[] args) {
Dog d1 = new Dog("peter");
Dog d2 = new Dog("peter");
Dog d3 = new Dog("peter");
System.out.println("自反性:d1.equals(d2)" + d1.equals(d2));
System.out.println("对称性:d1.equals(d2)" + d1.equals(d2));
System.out.println("对称性:d2.equals(d1)" + d2.equals(d1));
System.out.println("传递性:d1.equals(d2)" + d1.equals(d2));
System.out.println("传递性:d2.equals(d3)" + d2.equals(d3));
System.out.println("传递性:d1.equals(d3)" + d1.equals(d3));
System.out.println("一致性:");
for (int i=0;i<10;i++) {
if(d1.equals(d2)!=d1.equals(d2)){
System.out.println("没有遵守一致性原则");
}
}
System.out.println("遵守了一致性原则");
System.out.println("与null比较:" + d1.equals(null));
}
}
自反性:d1.equals(d2)false
对称性:d1.equals(d2)false
对称性:d2.equals(d1)false
传递性:d1.equals(d2)false
传递性:d2.equals(d3)false
传递性:d1.equals(d3)false
一致性:
遵守了一致性原则
与null比较:false
接下来我们看一下两个继承关系之间的父子类的equals方法,具体实现代码如下:
package com.lee.java;
public class Animal {
private String name;
@Override
public boolean equals(Object obj) {
if(obj instanceof Animal){
Animal animal = (Animal)obj;
return name == animal.name;
}
return false;
}
public Animal(String name) {
this.name = name;
}
}
package com.lee.java;
public class Dog extends Animal {
private Integer teeth;
public Dog(Integer teeth, String name) {
super(name);
this.teeth = teeth;
}
public static void main(String[] args) {
Animal animal = new Animal("lucy");
Dog dog = new Dog(20, "lucy");
System.out.println(animal.equals(dog));
System.out.println(dog.equals(animal));
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Dog) {
Dog d = (Dog) obj;
return super.equals(d) && teeth == d.teeth;
}
return false;
}
}
运行结果:
true false
从结果我们可以看出animal.equals(dog)
返回值是true,dog.equals(animal)
返回值是false,当把dog对象传入到父类animal类的equals方法中,我们可以看出其符合if判断并且返回true,因为dog类是animal类的子类,因此dog instanceof Animal
返回true。反之,当把animal对象传入到dog类的equals方法时,animal对象并不属于Dog类的对象,所以此时的animal instanceof Dog
会返回false。综合分析,我们可以知道前面所述就是导致true和false的原因所在。
equals和==
之间的区别:
- equals是一个方法,
==
是一个操作符 ==
是用来比较引用或者比较地址,equals比较的是内容。简单的来说就是==
比较两个对象是否指向的是内存中的同一块地址,equals比较的是两个对象所指向地址的内容来进行对比。- 如果一个类默认继承的Object类,且没有重写equals方法,那么使用equals和
==
是等价的。
重写equals方法的时候为何要重写hashcode方法?
简单了解一下hashcode方法,属于Object类,hashcode方法是根据对象来计算内存的地址值,也就是哈希码。Object类中的hashcode方法的具体实现:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
接下来我们看一个例子,具体实现代码如下:
package com.java;
public class TestHashCode {
public static void main(String[] args) {
String str1 = "peter";
StringBuilder sb1 = new StringBuilder(str1);
String str2 = new String("peter");
StringBuilder sb2 = new StringBuilder(str2);
System.out.println(str1.hashCode() + "===" + sb1.hashCode());
System.out.println(str2.hashCode() + "===" + sb2.hashCode());
}
}
运行结果:
106557964===1537587829
106557964===1567917652
从运行结果我们可以看出str1和str2的hashcode是相同的,但是sb1和sb2的hashcode是不一样的。出于什么原因呢?类似于equals方法,因为StringBuilder类并没有重写Object类的hashcode方法,所以StringBuilder的sb1和sb2调用的hashcode方法是OBject类的hashcode来计算其地址的,所以导致hashcode是不一样的。
那么我们应该如何来重写equals的hashcode方法呢?
- 只要合理地组织对象的散列码,就能够让不同的对象产生比较均匀的散列码。
- java 7中对hashCode方法做了两个改进,首先java发布者希望我们使用更加安全的调用方式来返回散列码,也就是使用null安全的方法
java.util.Objects.hashCode
(注意不是Object而是)方法,这个方法的优点是如果参数为null,就只返回0,否则返回对象参数调用的hashCode的结果。 java.util.Objects.hash
方法
下面是Objects类的hashcode和hash方法源码实现:
hashcode方法返回一个int类型的数值,参数是一个对象Object:
public static int hashCode(Object o) {
return o != null ? o.hashCode() : 0;
}
hash方法也返回一个int类型数值,只是它的参数是可变参数的对象,允许有多个对象作为参数传入进来:
public static int hash(Object... values) {
return Arrays.hashCode(values);
}
重写equals方法时,需要注意用instanceof还是用getClass方法:
在重写equals() 方法时,一般都是推荐使用 getClass 来进行类型判断(除非所有的子类有统一的语义才使用instanceof),不是使用 instanceof。我们都知道 instanceof 的作用是判断其左边对象是否为其右边类的实例,返回 boolean 类型的数据。可以用来判断继承中的子类的实例是否为父类的实现
总结一下:
- Object类中的equals和==的含义
- 如何重写equals方法
- 重写equals方法时需要遵守的规则
- 重写equals方法时为何要重写hashcode方法
- 如何重写hashcode方法
- 重写equals时getClass方法和instanceof的使用