当我们的使用任何Java的集合进行去重的时候,比如Set集合,比如JDK1.8的stream.distinct,Jvm都需要对不同的数据类型进行相等判断,如果我们往集合存入的是基本数据类型,那么很容易就可以判断相等,但是如果我们传入的是一个对象,那么需要重写相应类的equals方法和hashcode方法。
在JVM当中,比较两个对象的时候,是先调用相应类的hashCode方法计算两个对象的HashCode码,如果两个对象的HashCode码能够得出相同的两个值,那么再调用equals方法进行相等判断,如果hashCode码相同并且equals方法返回True,那么JVM就认为这两个对象是相等的。
然而我们应该如何去设计类的equals和hashCode方法呢?
一、equals方法
《Effective Java》中讲到,Java对Object.equals方法有以下必须要遵守的约定:
equals方法必须要满足以下几个等价关系
1.自反性:对于非null的x,x.equals(x)必须返回true
2.对称性:对于非null的x和y,当y.equals(x)返回true时,x.equals(y)必须返回true
3.传递性:对于非null的x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)必须返回true
4.一致性:对于非null的x和y,只要equals和x和y没有被修改,多次调用equals方法和一次调用的结果相同
在《Effective Java》中,作者针对每一条规则各列出了错误的equals写法,最后,给出了正确的equals写法:下面是Person类的equals方法
@Override
public boolean equals(Object obj){
if (obj == this){
return true;
}
if (!(obj instanceof Person)){
return false;
}
Person person = (Person)obj;
return person.age.equals(this.age)&&person.name.equals(this.name)&&person.sex.equals(this.sex);
}
注意点:
(1)传入的参数必须要是Object类,才可以覆盖Object类的equals方法
(2)使用instanceof关键字判断对象是否为同族类,如果不是同族类向下转型会失败
(3)判断完成之后将传入的Object向下转型为当前类,以便调用相应类的get方法
(4)满足相等条件的返回true
二、hashCode方法
hashCode方法同样有以下三条需要遵守的约定:
1.对于同一个对象,多次调用hashCode方法应该始终返回同一个整数
2.如果两个对象equals判断相等,那么hashCode方法产生的整数也必须要相同
3.如果equals方法比较不相等,那么hashCode方法产生的整数也可以相等
@Override
public int hashCode() {
int n=31;
n = n * 31 + this.name.hashCode();
n = n * 31 + this.age.hashCode();
n = n * 31 + this.sex.hashCode();
return n;
}
这里我name、age和sex属性都是String对象,所以计算的是他们的hashCode码。
对于每一个equals方法涉及的属性f
如果是boolean类型,计算f?1:0就可以了。
如果是byte、char、short或者int类型,则计算(int)f就可以。
如果是long类型,则计算(int)(f^(f>>>32))。
如果是double类型,则计算Double.doubleToLongBits(f),然后按照long类型处理就可以了。
如果是对象引用或者数组,则可以使用f.hashCode()。
在JVM层面,n*31会自动被优化成(n<<5)-i,所以这样计算性能很高。也可以乘以32或者33或者其他数,但习惯使用奇素数,Lombok自动生成的hashCode方法是使用的n*59。
如果一个类是不变类,并且计算hashCode码的开销也比较大,那么可以考虑将hashCode码缓存在对象内部,计算一次享用终生。
private volatile int hashCode;
@Override
public int hashCode() {
int n = hashCode;
if(n == 0){
n = 31;
n = n = n * 31 + this.name.hashCode();
n = n * 31 + this.age.hashCode();
n = n * 31 + this.sex.hashCode();
hashCode = n;
}
return n;
}
以上是hashCode和equals方法的设计原理,实际使用中,使用lombok自动生成的hashCode方法和equals就可以满足日常使用需求,附上找到的一篇lombok生成的hashCode和equals方法反编译: