欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java基础深度总结:Object类-hashCode方法

程序员文章站 2024-03-14 09:04:28
...
行动是治愈恐惧的良药,而犹豫、拖延将不断滋养恐惧。

1.Hash概述

Hash称为“散列”或者音译“哈希”,本质上是一种压缩映射,也就是说把任意长度的消息,通过散列算法压缩成某一固定长度的消息,由于散列的空间通常远小于输入的空间,因此不同的输入可能会散列出相同的输出。

2.hashCode的作用

hashCode方法返回一个hash code值,这个方法是为了更好的支持hash表,例如:HashMap、HashTable等,
在使用hash表这类数据结构时,首先需要通过hash函数计算出一个hash值,然后根据这hash值,放入到对应的Hash表对应的Key上,如果不同的对象确产生了相同的hash值,也就是发生了Hash值相同导致冲突的情况,那么就在这个Hash key的地方产生一个链表,将所有产生相同hash值的对象放到这个单链表上去,串在一起。

Java基础深度总结:Object类-hashCode方法

查找某个对象时,首先会计算出要查询对象的hash值,通过hash值确定该对象在哪个链表中,然后用equals方法依次判断该对象和链表上的对象是否相等。

  • 提高了对象的查询效率,只有在发生了hash冲突的情况下,才需要通过线性查找对象。
  • 如果两个对象相同,那么它们的hash值一定相同;如果两个对象的hash值相同,并不一定表示两个对象相同,但他们一定在同一个链表上。

Tips:

  • hashCode方法返回一个32位的int整数,hash值的取值范围是: -2^31 ~ 2^31 -1 ,因此若存在 2^32 + 1个对象,那么一定会有两个不同的对象有一样的hash值。
  • Object.hashCode返回的hash值不是对象内存地址。

3.为什么重写equals的同时还得重写hashCode(重点)

因为如果你只重写了equals方法而没有重写hashCode方法,那么在使用散列这样的数据结构时,没办法处理好你的键。这事实上违反了Object hashCode通约的第二条。

java.lnag.Object中对hashCode通约:

  1. 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。
  2. 如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。
  3. 如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。

下面举出了一个只重写了equals方法的例子:

import java.util.HashMap;
import java.util.Map;

public class EqualsAndHashCodeTest {

    public static void main(String[] args) {

        Map<Key, Integer> map = new HashMap<Key, Integer>();
        Key k1 = new Key("A");
        Key k2 = new Key("A");
        map.put(k1, 2);
        System.out.println("map.get(k1):" + map.get(k1));
        System.out.println("map.get(k2):" + map.get(k2));
        System.out.println("k1.equals(k2):" + k1.equals(k2));
        System.out.println("k1.hashCode:" + k1.hashCode());
        System.out.println("k2.hashCode:" + k2.hashCode());
    }

}

class Key {
    private String k;

    public Key(String key) {
        this.k = key;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        
        Key key = (Key) o;
        
        return k != null ? k.equals(key.k) : key.k == null;
    }
    
}
输出:
map.get(k1):2
map.get(k2):null
k1.equals(k2):true
k1.hashCode:21685669
k2.hashCode:2133927002

解析:
可以看到,k1和k2是等价的,但map.get(k2)并没有拿到我们想要取得的对象,因为Key并没有去重写hashCode方法,所以k1、k2的hash值是不一样的,所以以k2的hash值是无法正确找到k1 hash值所对应值的。这也说明了,如果你只实现equals方法而没有实现hashCode方法,那么将会给你在使用散列这类数据结构时埋下隐患!

4.hashCode的实现

1.把某个非0的常数值,比如17,保存在一个名为result的int类型的变量中。

2.对于对象中的每个域,做如下操作:

    a.为该域计算int类型的哈希值c:

  • 如果该域是boolean类型,则计算(f?1:0)
  • 如果该域是byte、char、short或者int类型,则计算(int)f
  • 如果该域是long类型,则计算(int)(f^(f>>>32))
  • 如果该域是float类型,则计算Float.floatToIntBits(f)
  • 如果该域是double类型,则计算Double.doubleToLongBits(f),然后重复第三个步骤。
  • 如果该域是一个对象引用,并且该类的equals方法通过递归调用equals方法来比较这个域,同样为这个域递归的调用hashCode,如果这个域为null,则返回0。
  • 如果该域是数组,则要把每一个元素当作单独的域来处理,递归的运用上述规则,如果数组域中的每个元素都很重要,那么可以使用Arrays.hashCode方法。

    b.把上面计算得到的hash值c 按照result = 31*result + c 公式 加入到result中

3.返回result

示例代码:

import java.util.Arrays;

public class HashCodeExample {

    private boolean flag;
    private int num;
    private long longNum;
    private double doubleNum;
    private int[] ids;
    private Student student;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        HashCodeExample that = (HashCodeExample) o;

        if (flag != that.flag) return false;
        if (num != that.num) return false;
        if (longNum != that.longNum) return false;
        if (Double.compare(that.doubleNum, doubleNum) != 0) return false;
        if (!Arrays.equals(ids, that.ids)) return false;
        return student != null ? student.equals(that.student) : that.student == null;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        //boolean
        result = (flag ? 1 : 0);
        //int
        result = 31 * result + num;
        //long
        result = 31 * result + (int) (longNum ^ (longNum >>> 32));
        //double
        temp = Double.doubleToLongBits(doubleNum);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        //数组
        result = 31 * result + Arrays.hashCode(ids);
        //引用
        result = 31 * result + (student != null ? student.hashCode() : 0);
        return result;
    }
}

class Student{
    private int id;
    private String name;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (id != student.id) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }

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

Tips:

  • 计算hash值时必须排除equals比较时没有用到的域,否则可能会违反Object.hashCode通约的第二条。
  • 31是个奇素数,而且31的乘法可以用移位和减法替代:31* i == ( i << 5 ) - i ,JVM会自动优化的。
  • hashCode和equals方法都可以用开发工具的快捷键生成的。
相关标签: Java基础