Java hashCode 与 equals 方法详解

Java hashCode 与 equals 方法详解

 

 

1、简介

我们知道Object是所有类的父类,所有的对象在不重写的情况下使用的是Objectequals方法和hashcode方法,从Object类的源码我们知道,默认的equals 判断的是两个对象的引用指向的是不是同一个对象;而hashcode也是根据对象地址生成一个整数数值;

 

2、hashCode介绍

 先用一张图看下什么是Hash?

Hash是散列的意思,就是把任意长度的输入,通过散列算法变换成固定长度的输出,该输出就是散列值。关于散列值,有以下几个关键结论:

  • 如果散列表中存在和散列原始输入K相等的记录,那么K必定在f(K)的存储位置上。
  • 不同关键字经过散列算法变换后可能得到同一个散列地址,这种现象称为碰撞。
  • 如果两个Hash值不同(前提是同一Hash算法),那么这两个Hash值对应的原始输入必定不同。

       hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int 整数。这个散列码的作用是确定该对象在散列表中的索引位置。hashCode() 定义在 JDK 的 Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。另外我们可以看到 Object 的 hashcode() 方法的修饰符为native,  表明该方法是操作系统实现,java调用操作系统底层代码获取哈希值。

       hashcode() 它是一个本地方法,它的实现与本地机器有关。当我们向一个集合中添加某个元素,集合会首先调用 hashCode 方法,这样就可以直接定位它所存储的位置,若该处没有其他元素,则直接保存。若该处已经有元素存在,就调用 equals 方法来匹配这两个元素是否相同,相同则不存,不同则散列到其他位置。这样处理,当我们存入大量元素时就可以大大减少调用 equals() 方法的次数,极大地提高了效率。

     数组是java中效率最高的数据结构,但是“最高”是有前提的。第一我们需要知道所查询数据的所在索引位置。   第二:如果我们进行迭代查找时,数据量一定要小,对于大数据量而言一般推荐集合。

    在 Java 集合中有两类,一类是 List,一类是 Set, 他们之间的区别就在于 List 集合中的元素是有序的,且可以重复,而 Set 集合中元素是无序不可重复的。对于 List 好处理,但是对于 Set 而言我们要如何来保证元素不重复呢? 通过迭代来 equals() 是否相等。数据量小还可以接受,当我们的数据量大的时候效率可想而知(当然我们可以利用算法进行优化)。比如我们向 HashSet 插入 1000 数据,难道我们真的要迭代 1000 次,调用 1000 次 equals() 方法吗?hashCode 提供了解决方案。

      所以 hashCode 在上面扮演的角色为寻域(寻找某个对象在集合中区域位置)。hashCode 可以将集合分成若干个区域,每个对象都可以计算出他们的 散列码,可以将 散列码分组,每个分组对应着某个存储区域(散列表),根据一个对象的 散列码就可以确定该对象所存储区域,这样就大大减少查询匹配元素的数量,提高了查询效率。

3、hashCode 对于一个对象的重要性

hashCode 重要么?对于 List 集合、数组而言不重要,但是对于 HashMap、HashSet、HashTable 而言,它变得异常重要。所以在使用 HashMap、HashSet、HashTable 时一定要注意 hashCode。对于一个对象而言,其 hashCode 过程就是一个简单的 Hash 算法的实现,其实现过程对你实现对象的存取过程起到非常重要的作用。

 
4、如何重写HashCode

Google首席Java架构师Joshua Bloch在他的著作《Effective Java》中提出了一种简单通用的hashCode算法
初始化一个整形变量,为此变量赋予一个非零的常数值,比如  int   result  =  17;
选取equals方法中用于比较的所有域,然后针对每个域的属性进行计算
(1) 如果是 boolean值,则计算 f ? 1 : 0
(2) 如果是 byte\char\short\int,则计算(int)f
(3) 如果是 long值,则计算(int)(f ^ (f >>> 32))
(4) 如果是 float值,则计算Float.floatToIntBits(f)
(5) 如果是 double值,则计算Double.doubleToLongBits(f),然后返回的结果是long,再用规则(3)去处理long,得到int
(6) 如果是对象引用,如果equals方法中采取递归调用的比较方式,那么hashCode中同样采取递归调用hashCode的方式。否则需要为这个域计算一个范式,比如当这个域的值为null的时候,那么hashCode 值为0
(7) 如果是数组,那么需要为每个元素当做单独的域来处理。如果你使用的是1.5及以上版本的JDK,那么没必要自己去重新遍历一遍数组,java.util.Arrays.hashCode方法包含了8种基本类型数组和引用数组的hashCode计算,算法同上,
  java.util.Arrays.hashCode(long[])的具体实现:

public static int hashCode(long a[]) {
        if (a == null)
            return 0;
 
        int result = 1;
        for (long element : a) {
            int elementHash = (int)(element ^ (element >>> 32));
            result = 31 * result + elementHash;
        }
 
        return result;
}

对于涉及到的各个字段,采用第二步中的方式,将其依次应用于下式:

result = result * 31 + [hashCode];

5、hashCode() 重写固定模板

故总结出hashCode()重写的固定模板如下:

/**
     * 重写hashCode方法
     */
    @Override
    public int hashCode() {
        int result = 17;
        // boolean 类型
        result = 31 * result + (this.mBoolean == flase ? 0 : 1);
        // int 类型
        result = 31 * result + this.mInt;
        // float 类型
        result = 31 * result + Float.floatToIntBits(this.mFloat);
        // long 类型
        result = 31 * result + (int)(this.mLong ^ (this.mLong >>> 32));
        // double 类型
        result = 31 * result + Float.valueOf(Double.doubleToLongBits(this.mDouble)).hashCode();
        // String 类型
        result = 31 * result + (this.mString == null ? 0 : this.mString.hashCode());
        // Object 类型
        result = 31 * result + (this.mObj == null ? 0 : this.mObj.hashCode());
        return result;
    }

6、如何重写equals()方法

重写覆盖父类Object.equals()方法,五步走:

  • 1.判断引用地址是否相同
  • 2.判断引用地址是否为空
  • 3.确认对象类型是否一致
  • 4.转型 - 向下转型拆箱
  • 5.比较对象中的实际内容
    @Override
    public boolean equals(Object obj) {
        // 相同判断
        if (this != obj) {
            return false;
        }
        
        // null判断
        if (obj == null) {
            return false;
        }
        
        // 类型一致判断
        if (this.getClass() != obj.getClass()) {
            return false;
        }
        
        // 拆箱操作(类型一致)
        MyUser w = (MyUser)obj;
        
        // 比较内容(比较所有成员的值|或者比较你认为对象相等的条件,对象中的字段相比较)
        if (this.toString().equals(w.toString())) {
            return true;
        }
        
        return false;
    }

 

7、hashCode 与 equals 总结

简而言之就是:

  • 如果两个对象相等,则hashcode一定也是相同的
  • 两个对象相等,对两个对象分别调用equals方法都返回true
  • 两个对象有相同的hashcode值,它们也不一定是相等的
  • 因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
  • hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

8、为什么重写Object的equals(Object obj)方法尽量要重写Object的hashCode()方法
 下面我们看一个具体的例子:
我们有一个MyUser对象:
@Data
public class MyUser {
    private String name;
    private Integer sex;
    private String date;
    public MyUser() {}
    public MyUser(String name) {
        this.name = name;
    }
    public MyUser(String name, Integer sex) {
        this.name = name;
        this.sex = sex;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof MyUser)) {
            return false;
        }
        MyUser myUser = (MyUser) o;
        return Objects.equals(getName(), myUser.getName()) && Objects.equals(getSex(), myUser.getSex());
    }
}

只要,MyUser对象的 name、sex 的equals 方法相同, 我们就认为这个对象相同。我们只重写了 Object 的equals方法, 没有重写hashCode方法, 下面我们往Set集合添加数据测试:

   public static void main(String[] args) {
        MyUser myUser1 = new MyUser("1");
        MyUser myUser2 = new MyUser("2");
        MyUser myUser3 = new MyUser("1");
        MyUser myUser4 = new MyUser("2");
        MyUser myUser5 = new MyUser("3");
        MyUser myUser6 = new MyUser("4");
        HashSet<MyUser> myUsers = new HashSet<>();
        myUsers.add(myUser1);
        myUsers.add(myUser2);
        myUsers.add(myUser3);
        myUsers.add(myUser4);
        myUsers.add(myUser5);
        myUsers.add(myUser6);
        System.out.println("myUsers.size()==" + myUsers.size());
    }

打印结果:

myUsers.size()==6

下面我们重写hashcode方法:

  // 省略以上代码...... 
 @Override
    public int hashCode() {
        return Objects.hash(getName(), getSex());
    }

再次测试如下:

 

 

 
 

 

posted @ 2021-03-12 00:13  邓维-java  阅读(578)  评论(0)    收藏  举报