首页 > 编程语言 >Java 如何重写对象的 equals 方法和 hashCode 方法

Java 如何重写对象的 equals 方法和 hashCode 方法

时间:2023-01-05 03:44:06浏览次数:37  
标签:return String 31 equals hashCode Java public

前言:Java 对象如果要比较是否相等,则需要重写 equals 方法,同时重写 hashCode 方法,而且 hashCode 方法里面使用质数 31。接下来看看各种为什么。

一、需求:

  对比两个对象是否相等。对于下面的 User 对象,只需姓名和年龄相等则认为是同一个对象。

二、解决方案:

  需要重写对象的 equals 方法和 hashCode 方法

package com.yule.user.entity; 
import org.springframework.util.StringUtils;
/** * 用户实体 * * @author wx * @date 2018/8/6 21:51 */ 
public class User {    
    private String id;   
    private String name;    
    private String age;     
    public User(){     }     
    public User(String id, String name, String age){        
        this.id = id;        
        this.name = name;        
        this.age = age;    }     
    public String getId() {        return id;    }     
    public void setId(String id) {        this.id = id;    }     
    public String getName() {        return name;    }     
    public void setName(String name) {        this.name = name;    }     
    public String getAge() {        return age;    }     
    public void setAge(String age) {        this.age = age;    }     
    @Override    public String toString() {        return this.id + " " + this.name + " " + this.age;    }     
    @Override    public boolean equals(Object obj) {        
        if(this == obj){            return true;//地址相等        }         
                        if(obj == null){            return false;//非空性:对于任意非空引用x,x.equals(null)应该返回false。        }         
                                        if(obj instanceof User){            User other = (User) obj;            //需要比较的字段相等,则这两个对象相等            
                                                                if(equalsStr(this.name, other.name)                    && equalsStr(this.age, other.age)){                return true;            }        }         
                                        return false;    }     
                        private boolean equalsStr(String str1, String str2){        
                            if(StringUtils.isEmpty(str1) && StringUtils.isEmpty(str2)){            return true;        }        
                            if(!StringUtils.isEmpty(str1) && str1.equals(str2)){            return true;        }        return false;    }     
                        @Override    public int hashCode() {        
                            int result = 17;        
                            result = 31 * result + (name == null ? 0 : name.hashCode());        
                            result = 31 * result + (age == null ? 0 : age.hashCode());        return result;    } }

三、测试

  1、创建两个对象,名字和年龄相等则对象 equals 为 true。

@Test    
public void testEqualsObj(){        
    User user1 = new User("1", "xiaohua", "14");        
    User user2 = new User("2", "xiaohua", "14");        
    System.out.println((user1.equals(user2)));//打印为 true    
}

四、为什么要重写 equals 方法

  因为不重写 equals 方法,执行 user1.equals(user2) 比较的就是两个对象的地址(即 user1 == user2),肯定是不相等的,见 Object 源码:

public boolean equals(Object obj) {        return (this == obj);    }

五、为什么要重写 hashCode 方法

  既然比较两个对象是否相等,使用的是 equals 方法,那么只要重写了 equals 方法就好了,干嘛又要重写 hashCode 方法呢?

  其实当 equals 方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。那这又是为什么呢?看看下面这个例子就懂了。

  User 对象的 hashCode 方法如下,没有重写父类的 hashCode 方法

@Override    
public int hashCode() {        return super.hashCode();    }

  使用 hashSet

@Test    
public void testHashCodeObj(){        
    User user1 = new User("1", "xiaohua", "14");        
    User user2 = new User("2", "xiaohua", "14");        
    Set userSet = new HashSet<>();        
    userSet.add(user1);        
    userSet.add(user2);        
    System.out.println(user1.equals(user2));        
    System.out.println(user1.hashCode() == user2.hashCode());        
    System.out.println(userSet);    }

  结果

0

  显然,这不是我们要的结果,我们是希望两个对象如果相等,那么在使用 hashSet 存储时也能认为这两个对象相等。

  通过看 hashSet 的 add 方法能够得知 add 方法里面使用了对象的 hashCode 方法来判断,所以我们需要重写 hashCode 方法来达到我们想要的效果。

  将 hashCode 方法重写后,执行上面结果为

@Override    
public int hashCode() {        
    int result = 17;        
    result = 31 * result + (name == null ? 0 : name.hashCode());        
    result = 31 * result + (age == null ? 0 : age.hashCode());        
    return result;    }

0

  所以:hashCode 是用于散列数据的快速存取,如利用 HashSet/HashMap/Hashtable 类来存储数据时,都会根据存储对象的 hashCode 值来进行判断是否相同的。

六、如何重写 hashCode

  生成一个 int 类型的变量 result,并且初始化一个值,比如17

  对类中每一个重要字段,也就是影响对象的值的字段,也就是 equals 方法里有比较的字段,进行以下操作:a. 计算这个字段的值 filedHashValue = filed.hashCode(); b. 执行 result = 31 * result + filedHashValue;

七、为什么要使用 31

  看一看 String hashCode 方法的源码:

/**     * Returns a hash code for this string. The hash code for a     * {@code String} object is computed as     * 
​
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
​
•     \* using {@code int} arithmetic, where {@code s[i]} is the     * *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(左边 312=62,右边 22^5-2=62) - 两边相等,JVM就可以高效的进行计算啦。。。

标签:return,String,31,equals,hashCode,Java,public
From: https://www.cnblogs.com/ynxiyan/p/17026460.html

相关文章

  • java生成jwt并使用RSA签名
    一、生成jwt在java中生成jwt的库用得比较多的是nimbus-jose-jwt、jose4j、java-jwt和jjwt(已迁移为jwt-api)。这里使用nimbus-jose-jwt。引入依赖:implementation("com.......
  • Open Source Customer Support Chat System Implementation Of Pop-up Effect JavaScr
    WhenIwasimplementingtheonlinecustomersupportchatsystem'spopupeffectJavaScriptSDK,theSDKcodethatwaspubliclyexposedwasintheformofaself......
  • JavaScript 自执行函数防止冲突全局作用域变量 - 在线客服源码实现弹窗效果JavaScript
    当我在实现在线客服源码弹窗效果JavaScriptSDK时,对外公开的SDK代码就是使用的自执行函数的形式。使用自执行函数来实现JavaScriptSDK有以下好处:封装代码:自执行函数......
  • 网站中引入了多个版本的 JavaScript 库防止对象冲突的方法 - 在线客服系统源码
    如果你在网站中引入了多个版本的JavaScript库,并且在你的JavaScript中使用了同名的对象,则可能会出现对象名称冲突的情况。使用命名空间来解决这个问题。例如,你可以在你......
  • 6.JavaScript HTML DOM和事件
    实验名称JavaScriptHTMLDOM和事件实验目的1.了解DOMHTML的概念和用法2.掌握DOM事件的用法实验原理HTMLDOM(文档对象模型)当网页被加载时,浏览器会创建页面的文档......
  • 5.JavaScript基础语法
    实验原理概念:JavaScript是世界上最流行的、轻量级的、脚本编程语言,可插入HTML页面,由浏览器执行。将这种脚本语言引入html,有三种方式:<script>与<script>标签,可被放置在HT......
  • Java相关问答
    简述Java中final关键字的三种用法。(1)final在类之前,表示该类是最终类,表示该类不能再被继承。(2)final在方法之前,表示该类方法是最终方法,该方法不能被任何派生的子类覆盖。......
  • hmac php java结果不一样问题
    比如我们有个服务是PHP提供的,要求的签名方式hmacSha256取摘要,然后Base64编码转化成可见字符。PHP那边的源码是这样的$result=base64_encode(hash_hmac("SHA256"......
  • hmacSha256 php java结果不一样问题
    比如我们有个服务是PHP提供的,要求的签名方式hmacSha256取摘要,然后Base64编码转化成可见字符。PHP那边的源码是这样的$result=base64_encode(hash_hmac("SHA256"......
  • Java8新特性-Lambda表达式
    Lambda表达式在Java语言中引入了一个操作符**“->”**,该操作符被称为Lambda操作符或箭头操作符。它将Lambda分为两个部分:左侧:指定了Lambda表达式需要的所有参数......