在前一篇文章《秒懂java,你真的会写equals方法吗?》中提到“覆盖equals时总要覆盖hashCode方法,避免一些预想不到的错误”。我们还是使用上篇文章的例子,修改一下User类后:
package cn.lovecto.test;
import java.util.HashMap;
public class User {
/** 用户名 */
private String name;
/** 手机号 */
private String phone;
public User(String name, String phone) {
super();
this.name = name;
this.phone = phone;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public boolean equals(Object obj) {
//1.使用“==”操作符检查参数是否是这个对象的引用
if (this == obj){
return true;
}
//2.检查参数是否为空
if (obj == null){
return false;
}
//3.检查参数类型是否匹配
if (getClass() != obj.getClass())
return false;
//4.把参数转换为正确的类型
User other = (User) obj;
//5.对于类中需要进行逻辑判断的属性,检查参数的属性是否与这个对象的属性相同
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (phone == null) {
if (other.phone != null)
return false;
} else if (!phone.equals(other.phone))
return false;
return true;
}
public static void main(String[] args) {
HashMap<User, User> userMap = new HashMap<User, User>();
userMap.put(new User("a", "135xxxxxxxx"), new User("a", "135xxxxxxxx"));
User u = userMap.get(new User("a", "135xxxxxxxx"));
System.out.println(u.getName() + "\t" + u.getPhone());
}
}
运行结果如下:
Exception in thread "main" java.lang.NullPointerException
at cn.lovecto.test.User.main(User.java:73)
User类中并没有重写hashCode方法,在main函数中,put的key和get的key调用equals肯定是相等的,但却存入HashMap后却找不到存入的对象了。出现这个的结果就是由于在重写
equals方法的同时没有重写hashCode方法导致的。
hashCode的通用约定
- 只要程序不挂,不重启,那么在程序执行期间,如果对象的equals方法的比较操作用到的所有信息均没有被修改,那么对同一个对象不管调用多少次,hashCode方法都必须返回同一个整数。同一个程序的多次执行,每次执行返回的整数可以不一样。
- 如果两个对象使用equals比较是相等的,那么调用这两个对象中的任意一个对象的hashCode方法都必须产生同样的整数结果。
- 如果两个对象使用equals比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生不同的整数结果(可以相同,最好不同)。给不相等的对象调用hashCode方法产生的整数结果不同,对提高散列表(hash table)的性能是有积极作用的。
重写hashCode的常规步骤
一个好的hashCode方法通常以“为不相等的对象产生不相等的hash值”为目标。理想情况下,hashCode方法应该把集合中的不相等的对象均匀地分布到所有可能的hash值上。一种常规的做法是:
- 把某个非零的常数值,比如1,保存在一个名为result的int类型变量中。
- 对于对象中每个关键的属性a(equals方法中涉及到的属性),完成下面的步骤:
(1)为该属性a计算int类型的hash值c:
如果a是boolean类型,则”c = a ? 1 : 0;”。
如果a是byte、char、short、int类型,则”c = (int)a;”。
如果a是long类型,则”c=(int)(a^(a>>>32));”。
如果a是float类型,则”c=Float.floatToIntBits(a);”。
如果a是double类型,则计算”c=(int)(Double.doubleToLongBits(a)^(Double.doubleToLongBits(a)>>>32));”。
如果a是一个对象引用,则使用该对象引用的hashCoder值作为c的值。
如果a是一个数组,这要把没一个元素作为单独的属性来处理,按照上面提到的规则计算hash值,再把这些值组合起来(组合方式参考(2))。
(2)按照如下的公式,把c的值合并到result中:
result = 31 * result + c; - 返回result。
- 写单元测试验证一下。
重写hashCode后,运行正常
我们在上面的实例中加入hashCode方法:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((phone == null) ? 0 : phone.hashCode());
return result;
}
再运行结果如下:
a 135xxxxxxxx
所以重写equals方法的同时切记要重写hashCode方法,不然后果将是非常的惨烈,得到非预想的结果。