在工作中,我们可能会遇到判断某一个对象是不是相等,而条件则是根据对象的几个字段的值来决定的。例如User对象,我们需要根据对象的userName判断两个对象是不是相等。
User.java
public class User {
private Integer id;
private String userName;
private Integer age;
private String desc;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public User(Integer id, String userName, Integer age, String desc) {
super();
this.id = id;
this.userName = userName;
this.age = age;
this.desc = desc;
}
public User() {
super();
}
}
解决方案:
一、最直接也是最蠢的办法,逐个比较对象的字段值是否相等,我们可以直接抛弃这种。
二、重写对象的equals的方法
@Override
public boolean equals(Object obj) {
//非空性:对于任意非空引用x,x.equals(null)应该返回false。
if(obj == null){
return false;
}
if(this == obj){//对象地址相等
return true;
}
if(obj instanceof User){
User user = (User)obj;
return fieldCheck(this.userName,user.userName);
}
return false;
}
private boolean fieldCheck(String param1,String param2){
//认为null值和""一样
if(StringUtils.isEmpty(param1) && StringUtils.isEmpty(param2)){
return true;
}
//非空值,两个值一样或者其中一个是空值
return !StringUtils.isEmpty(param1) && param1.equals(param2);
}
重写equals方法还需要重写hashCode方法
@Override
public int hashCode() {
return userName.hashCode();
}
上面只是一种简单的写法,当然为了提高运算效率,我们可以参考String的写法,改成下面的方式
@Override
public int hashCode() {
int result = 17;
return 31 * result + (StringUtils.isEmpty(userName)?0:userName.hashCode());
}
String源码:
/**
* Returns a hash code for this string. The hash code for a
* {@code String} object is computed as
* <blockquote><pre>
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* </pre></blockquote>
* using {@code int} arithmetic, where {@code s[i]} is the
* <i>i</i>th character of the string, {@code n} is the length of
* the string, and {@code ^} indicates exponentiation.
* (The hash value of the empty string is zero.)
*
* @return a hash code value for this object.
*/
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
分析:
可以从注释看出:空字符串的 hashCode 方法返回是 0。并且注释中也给了个公式,可以了解了解。
String 源码中也使用的 31,然后网上说有这两点原因:
原因一:更少的乘积结果冲突
31是质子数中一个“不大不小”的存在,如果你使用的是一个如2的较小质数,那么得出的乘积会在一个很小的范围,很容易造成哈希值的冲突。而如果选择一个100以上的质数,得出的哈希值会超出int的最大范围,这两种都不合适。而如果对超过 50,000 个英文单词(由两个不同版本的 Unix 字典合并而成)进行 hash code 运算,并使用常数 31, 33, 37, 39 和 41 作为乘子,每个常数算出的哈希值冲突数都小于7个(国外大神做的测试),那么这几个数就被作为生成hashCode值得备选乘数了。
所以从 31,33,37,39 等中间选择了 31 的原因看原因二。
原因二:31 可以被 JVM 优化
JVM里最有效的计算方式就是进行位运算了:
* 左移 << : 左边的最高位丢弃,右边补全0(把 << 左边的数据*2的移动次幂)。
* 右移 >> : 把>>左边的数据/2的移动次幂。
* 无符号右移 >>> : 无论最高位是0还是1,左边补齐0。
所以 : 31 * i = (i << 5) - i(左边 31*2=62,右边 2*2^5-2=62) - 两边相等,JVM就可以高效的进行计算啦。。。
好啦,说了这么多,下面来运行一下结果:
public class Test {
public static void main(String[] args) {
User u1 = new User(101,"W006733",23,"哈哈56哈哈-56");
User u2 = new User(102,"W006733",33,"哈哈56哈哈-56");
System.out.println(u1.equals(u2));
}
}
不重写equals和hashCode方法,运行结果:
u1==u2------->false
重写equals和hashCode方法,运行结果:
u1==u2------->true
完美的实现了我们想要的结果