equals和hashCode都是Object对象中的非final方法,它们设计的目的就是被用来覆盖(override)的,因此在程序设计中仍是常常须要处理这两个方法的。而掌握这两个方法的覆盖准则以及它们的区别仍是很必要的,相关问题也很多。面试
下面咱们继续以一次面试的问答,来考察对equals和hashCode的掌握状况。数组
==
运算符了,为何还须要equals啊?equals()的做用是用来判断两个对象是否相等,在Object里面的定义是:性能优化
public boolean equals(Object obj) {
return (this == obj);
}
复制代码
这说明在咱们实现本身的equals方法以前,equals等价于==
,而==
运算符是判断两个对象是否是同一个对象,即他们的地址是否相等。而覆写equals更多的是追求两个对象在逻辑上的相等,你能够说是值相等,也可说是内容相等。bash
在如下几种条件中,不覆写equals就能达到目的:dom
@Override public boolean equals(Object obj) { throw new AssertionError();}
这个我在Effective Java上看过,没记错的话应该是:ide
自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。函数
对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。性能
传递性:对于任何非空引用值 x、y 和 z,若是 x.equals(y) 返回 true, 而且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。单元测试
一致性:对于任何非空引用值 x 和 y,屡次调用 x.equals(y) 始终返回 true 或始终返回 false, 前提是对象上 equals 比较中所用的信息没有被修改。测试
非空性:对于任何非空引用值 x,x.equals(null) 都应返回 false。
对称性就是x.equals(y)时,y也得equals x,不少时候,咱们本身覆写equals时,让本身的类能够兼容等于一个已知类,好比下面的例子:
public final class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s) {
if (s == null)
throw new NullPointerException();
this.s = s;
}
@Override
public boolean equals(Object o) {
if (o instanceof CaseInsensiticeString)
return s.equalsIgnoreCase(((CaseInsensitiveString)o).s);
if (o instanceof String)
return s.equalsIgnoreCase((String) o);
return false;
}
}
复制代码
这个想法很好,想建立一个无视大小写的String,而且还可以兼容String做为参数,假设咱们建立一个CaseInsensitiveString:
CaseInsensitiveString cis = new CaseInsensitiveString("Case");
复制代码
那么确定有cis.equals("case")
,问题来了,"case".equals(cis)
吗?String并无兼容CaseInsensiticeString,因此String的equals也不接受CaseInsensiticeString做为参数。
因此有个准则,通常在覆写equals只兼容同类型的变量。
传递性就是A等于B,B等于C,那么A也应该等于C。
假设咱们定义一个类Cat。
public class Cat()
{
private int height;
private int weight;
public Cat(int h, int w)
{
this.height = h;
this.weight = w;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Cat))
return false;
Cat c = (Cat) o;
return c.height == height && c.weight == weight;
}
}
复制代码
名人有言,无论黑猫白猫抓住老鼠就是好猫,咱们又定义一个类ColorCat:
public class ColorCat extends()
{
private String color;
public ColorCat(int h, int w, String color)
{
super(h, w);
this.color = color;
}
复制代码
咱们在实现equals方法时,能够加上颜色比较,可是加上颜色就不兼容和普通猫做对比了,这里咱们忘记上面要求只兼容同类型变量的建议,定义一个兼容普通猫的equals方法,在“混合比较”时忽略颜色。
@Override
public boolean equals(Object o) {
if (! (o instanceof Cat))
return false; //不是Cat或者ColorCat,直接false
if (! (o instanceof ColorCat))
return o.equals(this);//不是彩猫,那必定是普通猫,忽略颜色对比
return super.equals(o)&&((ColorCat)o).color.equals(color); //这时候才比较颜色
}
复制代码
假设咱们定义了猫:
ColorCat whiteCat = new ColorCat(1,2,"white");
Cat cat = new Cat(1,2);
ColorCat blackCat = new ColorCat(1,2,"black");
复制代码
此时有whiteCat等于cat,cat等于blackCat,可是whiteCat不等于blackCat,因此不知足传递性要求。。
因此在覆写equals时,必定要遵照上述的5大军规,否则老是有麻烦事找上门来。
手写:
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;
}
复制代码
上面的equals有如下几点诀窍:
==
比较,float域用Float.compare方法,double域用Double.compare方法,至于别的引用域,咱们通常递归调用它们的equals方法比较,加上判空检查和对自身引用的检查,通常会写成这样:(field == o.field || (field != null && field.equals(o.field)))
,而上面的String里使用的是数组,因此只要把数组中的每一位拿出来比较就能够了。还有一些注意点。
hashCode用于返回对象的hash值,主要用于查找的快捷性,由于hashCode也是在Object对象中就有的,因此全部Java对象都有hashCode,在HashTable和HashMap这一类的散列结构中,都是经过hashCode来查找在散列表中的位置的。
以HashMap为例,使用的是链地址法来处理散列,假设有一个长度为8的散列表
0 1 2 3 4 5 6 7
复制代码
那么,当往里面插数据时,是以hashCode做为key插入的,通常hashCode%8获得所在的索引,若是所在索引处有元素了,则使用一个链表,把多的元素不断连接到该位置,这边也就是大概提一下HashMap原理。因此hashCode的做用就是找到索引的位置,而后再用equals去比较元素是否是相等,形象一点就是先找到桶(bucket),而后再在里面找东西。
一个好的hashCode的方法的目标:为不相等的对象产生不相等的散列码,一样的,相等的对象必须拥有相等的散列码。
好的散列函数要把实例均匀的分布到全部散列值上,结合前人的经验能够采起如下方法:
引自Effective Java
把某个非零的常数值,好比17,保存在一个int型的result中;
对于每一个关键域f(equals方法中设计到的每一个域),做如下操做:
a. 为该域计算int类型的散列码;
i.若是该域是boolean类型,则计算(f?1:0),
ii.若是该域是byte,char,short或者int类型,计算(int)f,
iii.若是是long类型,计算(int)(f^(f>>>32)).
iv.若是是float类型,计算Float.floatToIntBits(f).
v.若是是double类型,计算Double.doubleToLongBits(f),而后再计算long型的hash值
vi.若是是对象引用,则递归的调用域的hashCode,若是是更复杂的比较,则须要为这个域计算一个范式,而后针对范式调用hashCode,若是为null,返回0
vii. 若是是一个数组,则把每个元素当成一个单独的域来处理。
复制代码
b.result = 31 * result + c;
返回result
编写单元测试验证有没有实现全部相等的实例都有相等的散列码。
这里再说下2.b中为何采用31*result + c
,乘法使hash值依赖于域的顺序,若是没有乘法那么全部顺序不一样的字符串String对象都会有同样的hash值,而31是一个奇素数,若是是偶数,而且乘法溢出的话,信息会丢失,31有个很好的特性是31*i ==(i<<5)-i
,即2的5次方减1,虚拟机会优化乘法操做为移位操做的。