重写equals()方法
为什么重写equals方法?我们先来看看Object类是如何实现equals()方法的:

Object类的equals方法
就是这么简单。根据Object类默认实现的equals方法, 类的每个实例都只于它自身相等 。这在一定情况下是合适的实现,比如类的每个实例本质上都是唯一的,像Thread类、Requests类等。但在更多的情况下这个equals方法并不合适。比如下面这种情况:
public class MyClass {
public static void main(String[] args) {
Person p1 = new Person(" 特朗普 ","123456789");
Person p2 = new Person("特朗普","123456789");
System.out.println(p1.equals(p2));
}
}
class Person{
String name;
String idCard;
Person(String name, String idCard){
this.name = name;
this.idCard = idCard;
}
public String getName(){ return this.name; }
public String getIdCard(){ return this.idCard; }
}
上面的代码输出的是false。按我们的想法,一个人的名字和身份证号唯一可以确定一个人,所以这里p1和p2应该在逻辑上相等。但如图所示,因为Object默认equals方法的原因,返回的是false。一般类似这种情况,我们需要重写equals方法。
我们为上面例子的类重写equals方法:
class Person{ ... @Override public boolean equals(Object o){ if(o instanceof Person){ Person obs = (Person)o; if(obs.getName().equals(this.name) && obs.getIdCard().equals(this.idCard)){ return true; } return false; } return false; } ... }
我们只需要在Person类中“添加”(重写)这个equals方法,那么程序就会按照我们的意愿输出true。因为我们的想法是身份证号和姓名一致就是同一个人,所以我们在equals方法里面判断如果入参和自身的这两个字段值相等,我们就返回true即可。
equals方法我们可以理解为数学上的=号,那么在实现equals方法时,我们可以类比=号保证equals必须满足下面几个条件:
- 自反性:如果x对象不为null,那么x.equals(x)必须保证返回true
- 对称性:如果x和y非空,那么x.equals(y)返回true时,y.equals(x)也必须保证返回true
- 传递性:如果x、y、z非空,那么当x.equals(y)和y.equals(z)返回true时,x.equals(z)也必须保证返回true
- 一致性:x和y非空,且信息没有被修改过,那么不同时间段x.equals(y)返回的值必须要相同
- null规定:如果x非空,x.equals(null)必须保证返回false
上面这些其实很好理解,比如如果我们实现的equals方法不满足自反性,那么所有有关去重或者查找的方法将可能不起作用。
为了引出下一个标题,这里举一个例子:

Set里放Person对象
我们已经为Person添加了equals方法,但这段代码还是会返回false,并不是true。为什么?
我先说答案:因为没有实现 hashCode ()方法。我们看HashSet:

HashSet是用hashMap保存数据的
HashSet内部是用HashMap保存数据的。

hashSet的contains方法
HashSet的contains方法是直接调用map的containsKey方法实现的。

HashMap的containsKey方法
containsKey中的hash()方法需要调用入参对象的hashCode()方法。

HashMap hash方法
到这里,不需要再进入getNode方法,相信大家也知道原因了。
重写hashCode()方法
我们都知道:重写equals方法后一定要重写hashCode方法。为什么呢?我们来看看Object源码:

jdk8 中的 hashCode()
图中hashCode(规范总结一下就是三点:
- 在应用程序执行期间,如果对象equals方法比较操作用到的信息没有被修改,那么对同一个对象调用多次,hashCode方法必须保证返回同一个整数。用上面的Person例子,只要p1对象的name和idCard没有修改,那么多次调用p1的hashCode必须一致返回同一个值。但这里有一个例外,如果应用程序重启了,那么重启后对象hashCode返回的值可以和重启前不一样。
- 如果两个对象通过equals方法比较返回true,那么这两个对象的hashCode也必须返回同一个整数。其实就一句话:相等的对象必须具有一致的散列码。否则当该类的对象需要用到基于散列的操作时,就会出问题。
- 如果两个对象通过equals方法比较返回false,它们的hashCode可以返回同一个整数。其实这个就是hash冲突。虽然不相等的对象可以返回同一个散列值,但减少这种情况有利于提高 散列表 的性能。
重写hashCode()方法很简单,@Override public int hashCode(){return 1;}这就实现了重写hashCode,它把所有对象散列为1.当然这种做法是超级烂的,只是用来说明重写hashCode的方法。重写hashCode涉及到hash算法,这个相信了解过数据结构的人都知道,什么数字分析法、折叠法、平方取中法等等。都学过的。
String实现的equals和hashCode方法
最后拿String对象来学习一下规范的equals方法和hashCode方法如何写:

String 的equals方法
我在 总结String是不可变对象时,提到String的属性中除了hash属性外都是final的。这是为什么?这涉及到一个优化思路:如果一个对象是不可变的,且计算散列码消耗比较大,那么应该把对象的散列码缓存到对象内部,而并非每次都重新计算。

String 的hashCode方法