equals与hashCode
Object类中的equals方法和hashCode方法
equals() 在没有重写前与==作用相同
/**
* Object类中的equals方法
*/
public boolean equals(Object obj) {
return (this == obj);
}
hashCode()为本地方法:
public native int hashCode();
重写规则
- 对象相同,hashCode一定相同
- hashCode相同,对象不一定相同
- 对象相同,equals的返回值一定为true
因此,重写equals时一定要重写hashCode
String类中重写后的equals方法和hashCode方法
equals
重写后的equals可自定义规则,一般equals比较内容
/**
* String类中的equals方法
*/
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;
}
hashCode
String类重写了equals方法后一定会重写hashCode方法:
/**
* String类中的hashCode方法
*/
public int hashCode() {
// private int hash; // Default to 0
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
// s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
for (int i = 0; i < value.length; i++) {
// 为什么用31:
// 1. 不大不小的质数既可以减少哈希冲突又可以使计算出的哈希值不会超出整数范围
// 2. 31可以被jvm优化计算,31 = 1 << 5 - 1
h = 31 * h + val[i];
}
hash = h;
}
// 空串的hashCode值为0
return h;
}
自定义类型该如何重写equals方法和hashCode方法
当我们自定义了一个类后,若要判断该类中的两个对象是否相同时,即将自定义类型对象作为HashMap的key值时或将自定义类型对象存入HashSet中去重时,就一定要重写equals和hashCode
自定义类型
/**
* 自定义UserInfo类
*/
public class UserInfo {
private int id;
private String name;
private int sex;
// setter and getter
}
重写equals
/**
* 重写equals方法遵循下面4个部分即可
* 1.保证自反性
* 2.非空判断
* 3.是否为该类对象
* 4.自定义规则
*/
@Override
public boolean equals(Object o) {
// 1.自反性
if(this == o) {
return true;
}
// 2.任何非空对象都不与null相等
if(o == null) {
return false;
}
// 3.对象属于该类
if(o instanceof UserInfo) {
UserInfo userInfo = (UserInfo) o;
// 4.自定义规则:name 和 sex 均相同返回true
if(userInfo.name.equals(this.name) && userInfo.sex == this.sex) {
return true;
}
}
return false;
}
重写hashCode
/**
* 重写hashCode方法,仿照String类的重写方式:
* 1.指定hash的初始值
* 2.将equals中用到的变量参与运算该对象的哈希值:hash += 31 * hash + hash(field)
*/
@Override
public int hashCode() {
int hash = 0;
hash += 31 * hash + ((name == null) ? 0 : name.hashCode());
hash += 31 * hash + sex;
return hash;
}
为什么重写equals时一定要求重写hashCode
由于HashSet判重的顺序是先比较两个对象的hashCode是否相同,若不同则判定为对象不相同;若相同,继续判断两个对象equals的返回值,若返回true,则认为两个对象相同;否则认为两个对象不同。
未重写hashCode出现的情况
例如上面的UserInfo类,若只重写equals方法而未重写hashCode方法,会导致两个对象的equals方法返回值虽然是true,但由于未重写hashCode方法导致两个对象的哈希值不相同,从而存入HashSet中时不会达到去重效果,代码如下:
/**
* 自定义类UserInfo,只重写了equals方法,未重写hashCode方法
*/
public class UserInfo {
private int id;
private String name;
private int sex;
public UserInfo(int id, String name, int sex) {
this.id = id;
this.name = name;
this.sex = sex;
}
@Override
public boolean equals(Object o) {
// 1.自反性
if(this == o) {
return true;
}
// 2.任何非空对象都不与null相同
if(o == null) {
return false;
}
// 3.是否为该类对象
if(o instanceof UserInfo) {
UserInfo userInfo = (UserInfo) o;
// 4.自定义规则:name和sex均相同时返回true
if(userInfo.name.equals(this.name) && userInfo.sex == this.sex) {
return true;
}
}
return false;
}
}
测试代码:
public class UserInfoTest {
@Test
public void test() {
UserInfo user1 = new UserInfo(1, "tom", 1);
UserInfo user2 = new UserInfo(2, "tom", 1);
// 查看equals是否生效
System.out.println(user1.equals(user2));
Set<UserInfo> set = new HashSet<UserInfo>();
set.add(user1);
set.add(user2);
// 判断能否去重
System.out.println("size: " + set.size());
System.out.println(set);
}
}
控制台打印内容:
true
size: 2
[object.UserInfo@7e0b37bc, object.UserInfo@3b95a09c]
可以看到equals虽然返回的结果为true但是set中仍然有两个对象,这不是我们想要的结果。
重写后达到效果
重写hashCode的控制台打印内容:
true
size: 1
[object.UserInfo@382a41]
因此重写equals时一定要重写hashCode。