Bootstrap

hashCode() 有什么用?为什么要有 hashCode?

hashCode() 尤其是在处理集合(如 HashSetHashMapHashtable 和 LinkedHashMap 等)时。这些方法背后的原理基于哈希表,而哈希表是一种使用哈希函数组织数据,以支持快速插入和查找的数据结构。其作用是获取哈希码(int 整数),也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。

hashCode() 方法的作用:

  1. 确定对象的存储位置:在基于哈希表的集合中,对象的存储位置是通过调用对象的 hashCode() 方法,并将返回的哈希码值经过某种算法(如取模运算)转换成数组的下标来确定的。这有助于快速定位到对象在集合中的存储位置,从而提高存取效率。

    hashCode() 方法

  2. 辅助判断对象相等性:虽然 hashCode() 方法本身不直接用于判断两个对象是否相等,但它是 equals(Object obj) 方法的辅助。根据 Java 的规范,如果两个对象通过 equals(Object obj) 方法比较相等,那么调用这两个对象中任意一个对象的 hashCode() 方法必须产生相同的整数结果。这被称为 hashCode() 方法的一致性约定。

  3. 优化性能:在哈希表中,如果不同的对象通过 hashCode() 方法计算得到相同的哈希码(即发生了哈希冲突),这些对象会被存储在同一个哈希桶(bucket)中。此时,通常需要通过调用 equals(Object obj) 方法来进一步比较这些对象是否相等,以确定是否真正为同一个对象。因此,合理设计 hashCode() 方法以减少哈希冲突,可以显著提高哈希表的性能。

注意事项:

  • 当你重写 equals(Object obj) 方法时,通常也需要重写 hashCode() 方法,以保持 hashCode() 方法的一致性约定。
  • hashCode() 方法生成的哈希码应该是整数,并且在对象的生命周期内保持不变(只要对象的信息没有被修改),除非对象被用于某些依赖于对象哈希码信息的集合中,且从集合中移除了对象(此时对象的哈希码是否改变取决于具体的实现)。
  • 默认的 hashCode() 方法(继承自 Object 类)为不同对象产生不同的整数,但这并不保证不同对象会有不同的哈希码。因此,在需要高性能哈希表操作的场合,应该根据对象的具体内容来重写 hashCode() 方法。

为什么要有 hashCode?

hashCode 的存在主要是为了优化基于哈希表的集合(如 HashSetHashMapHashtable 等)的性能。这些集合通过哈希表来存储元素,而哈希表利用哈希函数来快速定位元素的存储位置。hashCode 方法就是用来生成这个哈希值的:

  1. 快速定位:哈希表通过计算元素的哈希值来快速定位到元素在表中的存储位置(通常是数组的一个索引)。这样,查找、插入和删除操作的时间复杂度可以接近 O(1),这比线性搜索(如数组或链表的遍历)要快得多。

  2. 减少冲突:虽然理想情况下,不同的元素应该映射到不同的哈希值上,但在实际中,由于哈希值的范围有限(通常是 int 类型),不同的元素可能会产生相同的哈希值,这称为哈希冲突。hashCode 方法的设计应该尽量减少这种冲突,以提高哈希表的性能。

  3. 支持高效的集合操作:集合框架中的许多操作(如 containsremove 等)都依赖于 hashCode 方法。例如,在 HashSet 中查找一个元素时,首先会计算该元素的哈希值,然后定位到对应的哈希桶中,最后在该桶中通过 equals 方法查找具体的元素。

  4. 一致性约定:Java 规范要求,如果两个对象通过 equals 方法比较相等,那么它们的 hashCode 方法必须返回相同的整数结果。这是为了保持哈希表的正确性,因为如果相等的对象有不同的哈希码,那么它们可能会被放置在哈希表的不同位置,导致无法正确检索或更新这些对象。

  5. 优化内存使用:哈希表通过动态调整其内部结构(如扩容)来适应元素数量的变化。如果 hashCode 方法能够均匀分布哈希值,那么哈希表就可以更有效地使用其内存空间,减少因哈希冲突而导致的空间浪费。

例子:使用自定义对象作为HashMap的键

假设我们有一个Person类,它有两个属性:nameage。我们想要将Person对象作为键存储在HashMap中,并使用这些键来快速查找与它们相关联的值。

public class Person {  
    private String name;  
    private int age;  
  
    public Person(String name, int age) {  
        this.name = name;  
        this.age = age;  
    }  
  
    // 必须重写hashCode()方法以确保HashMap能够正确地存储和检索键  
    @Override  
    public int hashCode() {  
        final int prime = 31;  
        int result = 1;  
        result = prime * result + age;  
        result = prime * result + ((name == null) ? 0 : name.hashCode());  
        return result;  
    }  
  
    // 通常也需要重写equals()方法,以保持hashCode()方法的一致性约定  
    @Override  
    public boolean equals(Object obj) {  
        if (this == obj) return true;  
        if (obj == null || getClass() != obj.getClass()) return false;  
        Person other = (Person) obj;  
        if (age != other.age) return false;  
        if (name == null) {  
            if (other.name != null) return false;  
        } else if (!name.equals(other.name)) return false;  
        return true;  
    }  
  
    // 省略getter和setter方法  
}  
  
// 使用Person对象作为HashMap的键  
public class HashMapExample {  
    public static void main(String[] args) {  
        HashMap<Person, String> map = new HashMap<>();  
  
        Person p1 = new Person("Alice", 30);  
        Person p2 = new Person("Bob", 25);  
  
        map.put(p1, "Developer");  
        map.put(p2, "Designer");  
  
        // 假设我们想要查找与Alice对应的职业  
        Person searchKey = new Person("Alice", 30);  
        String job = map.get(searchKey);  
  
        if (job != null) {  
            System.out.println("Alice is a " + job);  
        } else {  
            System.out.println("Alice's job is not found");  
        }  
  
        // 输出:Alice is a Developer  
    }  
}

解释

  1. hashCode()方法的作用:在上面的例子中,hashCode()方法被重写以确保Person对象能够正确地映射到HashMap的哈希表中。它通过结合nameage属性的哈希码来计算整个对象的哈希码。这样,当我们在HashMap中查找一个Person对象时,HashMap可以快速定位到该对象可能存在的哈希桶中,然后通过equals()方法进一步确认是否找到了正确的对象。

  2. equals()方法的作用:虽然hashCode()方法用于定位,但equals()方法用于在哈希桶中确认两个对象是否相等。在这个例子中,equals()方法检查两个Person对象的nameage属性是否都相等。

  3. 为什么需要重写这两个方法:Java的HashMapHashSet等集合类使用对象的哈希码来优化存储和检索性能。默认情况下,对象的哈希码是基于对象的内存地址计算的,这对于自定义对象来说通常不是很有用。因此,当我们使用自定义对象作为这些集合的键或元素时,我们需要重写hashCode()equals()方法以确保集合能够正确地工作。

  4. 性能优化:一个好的哈希函数应该能够均匀地分布哈希值,以减少哈希冲突并提高性能。在上面的例子中,我们使用了一个常见的技巧,即将不同的字段(nameage)的哈希码组合起来,并使用一个质数(如31)作为乘数来减少哈希冲突的可能性。

;