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

一、需求:

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

二、解决方案:

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

package com.yule.user.entity;

import org.springframework.util.StringUtils;

/**
* 用户实体
*
* @author yule
* @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<User> 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);
}

  结果

  显然,这不是我们要的结果,我们是希望两个对象如果相等,那么在使用 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;
}

  所以: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
* <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就可以高效的进行计算啦。。。

Java 如何重写对象的 equals 方法和 hashCode 方法的更多相关文章

  1. Java基础系列-equals方法和hashCode方法

    原创文章,转载请标注出处:<Java基础系列-equals方法和hashCode方法> 概述         equals方法和hashCode方法都是有Object类定义的. publi ...

  2. HashSet中存方用户自己定义数据类型数据,重写equals方法和hashCode方法

    import java.util.Set; import java.util.HashSet; public class SetTest { public static void main(Strin ...

  3. 详解equals()方法和hashCode()方法

    前言 Java的基类Object提供了一些方法,其中equals()方法用于判断两个对象是否相等,hashCode()方法用于计算对象的哈希码.equals()和hashCode()都不是final方 ...

  4. 关于Object类的equals方法和hashCode方法

    关于Object类的equals的特点,对于非空引用: 1.自反性:x.equals(x) return true : 2.对称性:x.equals(y)为true,那么y.equals(x)也为tr ...

  5. 详解 equals() 方法和 hashCode() 方法

    创建实体类时,最好重写超类(Object)的hashCode()和equals()方法 equals()方法: 通过该实现可以看出,Object类的实现采用了区分度最高的算法,即只要两个对象不是同一个 ...

  6. equals()方法和hashCode()方法详解

    equals()方法和hashCode()方法详解 1. Object类中equals()方法源代码如下所示: /** * Object类中的equals()方法 */ public boolean ...

  7. JAVA正确地自定义比较对象---如何重写equals方法和hashCode方法

    在实际应用中经常会比较两个对象是否相等,比如下面的Address类,它有两个属性:String province 和 String city. public class Address { priva ...

  8. Java equals()方法和hashCode()方法

    equals()方法 如果满足了以下任何一个条件,就不需要覆盖equals()方法: 1 类的每个实例本质上都是唯一的. 2 不关心类是否提供了“逻辑相等”的测试功能. 3 父类已经覆盖了equals ...

  9. java中equals方法和hashcode方法的区别和联系,以及为什么要重写这两个方法,不重写会怎样

    一.在Object类中的定义为:public native int hashCode();是一个本地方法,返回的对象的地址值.但是,同样的思路,在String等封装类中对此方法进行了重写.方法调用得到 ...

随机推荐

  1. jzoj5894

    先前綴和一發,問題表示求[0-l2][0-r2]滿足條件的數的個數 假設可以把某一個數拆分成[前面任意個數][00-0-11-1(個數相同)]的區間 那麼問題會簡單的多,因為任意一個a位的整數分別xo ...

  2. hdu4815----dp0-1背包

    /* 题目大意: 有n个问题,,告诉你答对该题能得多少分,其中一个人随机答题,问另一个人不输的概率为p 至少需要答多多少分 对于样例: 3 0.5 1 2 3 分析: 分数 0 1 2 3 3 4 5 ...

  3. 八,mysql优化——读写分离

    读写分离目的是给大型网站缓解查询压力.

  4. Sticky Fingure安装教程

    作者:小离 官方对应Sticky Finger Kali-Pi 的介绍: Sticky Fingers Kali-Pi – The pocket size, finger friendly, lean ...

  5. Exploit之初识Linux下缓冲区溢出

    本文的目的不是为了介绍如何进行恶意的破坏性活动,而是为了教会你如何去防御此类破坏性活动,以帮助你扩大知识范围,完善自己的技能,如有读者运用本文所学技术从事破坏性活动,本人概不负责. 0×01 前言 1 ...

  6. linux查看防火墙状态及开启关闭命令

    存在以下两种方式: 一.service方式 查看防火墙状态: [root@centos6 ~]# service iptables status iptables:未运行防火墙. 开启防火墙: [ro ...

  7. centos6下无法使用lsof命令"-bash: lsof: command not found"

    1. 问题描述在centos下, 无法使用命令lsof, 出现以下信息:# lsof -i:3690-bash: lsof: command not found2. 解决方法我们可以通过yum来安装: ...

  8. php的GC机制

    在php5.3版本之前, php变量的回收机制只是简单的通过计数来处理(当refcount=0时,会回收内存),但这样会出现一个问题 $a=array("str"); $a[]=& ...

  9. 【xsy3423】党² 线段树+李超线段树or动态半平面交

    本来并不打算出原创题的,此题集CF542A和sk的灵感而成,算个半原创吧. 题目大意: 给定有$n$个元素的集合$P$,其中第$i$个元素中包含$L_i,R_i,V_i$三个值. 给定另一个有$n$个 ...

  10. 详解Python中的join()函数的用法

    函数:string.join() Python中有join()和os.path.join()两个函数,具体作用如下:     join():    连接字符串数组.将字符串.元组.列表中的元素以指定的 ...