Object自带的equals方法是用来比较两个对象的引用地址是否一致的,往往我们自己的对象所需要的equals方法需要一些特定的条件去判断是否是同一个对象,这就需要通过重写我们自定义方法的equals方法来实现了。
一:Object类
首先让我们一起看看Object中equals和hashCode是如何实现的
public boolean equals(Object obj) {
return (this == obj);
}
public native int hashCode();
Java的公共父类Object的equals方法是通过比较两个对象的引用地址来判断两个对象是否相同,如果我们新建了一个新的类但是不重写equals方法,默认继承Object的equals方法。而Object的hashCode方法是调用本地方法,其底层是由C++实现,有兴趣的同学可以去研究一番。
二:String类
然后再来看看String类的equals和hashCode方法
public boolean equals(Object anObject) {
//判断两个对象的引用地址是否一致
if (this == anObject) {
return true;
}
//判断传入的对象是否是String类型
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;
}
public int hashCode() {
int h = hash;
//首先判断本对象是否已经计算过hash值并且字符串的长度大于0
if (h == 0 && value.length > 0) {
char val[] = value;
//循环字符串中的字符,用以计算hash值
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
String类是我们常用的java类,java中的所有类默认继承Object,String类重写了Object类中的equals和hashCode方法。equals中首先判断被比较的两个对象的引用地址是否一致,如果一致则说明两个对象相等,如果不一致则再判断传入的对象是否是String类型,若是String类型的话则开始比较两个对象的长度和字符串中每一个字符是否一致,这些都一直则说明两个比较的对象是相等的。而计算hash值的hashCode方法通过循环字符串中的字符来计算出字符串对象的hash值。
三:自定义Coder类
看到了java中其它类的equals和hashCode方法的具体实现,现在我们来创建属于我们自己的类,并且重写equals和hashCode方法吧。期待的搓手手(★ ω ★)~
public class Coder {
private String name;
private int age;
public Coder(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int hashCode() {
int result = 17;
result = result * 31 +name.hashCode();
result = result * 31 + age;
return result;
}
@Override
public boolean equals(Object obj) {
//判断传入的对象和本对象引用地址是否一致
if(obj == this){
return true;
}
//判断传入的对象是否属于Coder类
if(!(obj instanceof Coder)){
return false;
}
//判断两个对象的name和age属性是否一致
Coder coder = (Coder)obj;
return name.equals(coder.getName())&&age==coder.getAge();
}
}
以上便是自定义Coder类,由上可以看到,Coder类一共有两个属性,name和age,如果我们不重写equals方法的话,Coder类的equals方法默认继承Object的通过比较两个对象的引用地址来判断对象是否相等,但是这并不是我想要的,我想要通过Coder类的name和age属性来判断两个Coder对象是否相等,于是便重写了equals方法。重写的算法是仿照String类来写的,首先判断两个对象的引用地址是否一致,再判断传入的对象是否是Coder类,最后判断两个Coder的name和age属性是否一致,这样就能判断出两个Coder对象是否相等。hashCode方法是以name和age为基础,计算出hash值,这样就能保证在name和age相同的情况下,返回的hash值也是相等的。
为什么要重写hashCode()?
按我上面所说,似乎我们只需要重写equals方法就足够了,其中重写hashCode又有什么意义呢,如果同学们看到我之前写得HashMap的博客,就会理解为什么要重写hashCode方法了。
HashMap是一种存储数据结构,以key-value格式存储数据,其中key值可以是不同种类型,如果把key值直接存入数组中,我们每次查询的时候就要遍历整个数组,这样的性能消耗是很大的,但是HashMap以数组+链表的组合模式来存储数据,用key值计算出hash值,再使用hash值来计算出数组中元素的数组下标,这样就可以直接命中对应的元素所在的链表而不需要去遍历。
如果我们在重写对象的equals方法时不重写hashCode方法,那么该对象存入有关hash的存储数据结构是有问题的,因为这些数据结构需要根据hashCode方法计算的hash值去存储元素,在不重写hashCode的情况下,equals比较为相等的两个元素,存储在HashMap中的位置有极大可能不在同一个位置,所以有可能在HashMap中找不到对应的元素。