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

自定义hashCode()

程序员文章站 2022-03-15 20:53:08
...

参考目录:

1. HashMap 散列初体验
2. 为什么HashMap 常用String 对象作key
3. HashMap 原理
4.自定义 hashCode()
5.HashMap 的性能因子

在我们了解如何散列之后,编写自己的hashCode() 就显得更有意义了。设计hashCode() 时最重要的因素就是:无论何时对同一个对象在调用时产生的hashCode() 都应该产生同样的值。如果将一个对象用put() 方法添加进HashMap 时产生一个hashCode() 值,而用get() 方法取出时却产生另一个hashCode() 值,那么就无法取出该对象了。

所以在覆盖hashCode() 时如果我们产生的散列码依赖于对象中异变的数据(这个异变的数据指的是在当前这个对象中),那我们就应该当心了,因为数据变化时,hashCode() 就会产生一个不同的散列码,相当于产生了一个不同的键。此外也不应该使hashCode() 依赖于具有唯一性的对象信息,尤其是使用this 的值,这样只会产生更糟糕的hashCode()。

要想使hashCode() 实用,它必须速度很快,并且必须有意义。也就是说它必须基于对象的内容生成散列码。散列码不一定必须是独一无二的,但是要能通过hashCode() 和equals() 确定对象的唯一身份。另一个重要的因素是:好的hashCode() 方法应该产生均匀的散列码。如果散列码都集中在某一块,那么HashMap 和 HashSet 在某些区域就会负载很严重,没有分布均匀的散列码块。

怎么写出一份像样的hashCode() 呢,我们可以参考下面的这些条件:
1. 给int 变量result 赋予某个非零值常量
2. 为对象内每个有意义的域f(即每个可以做equals() 操作的域)计算出一个int 散列码c

域类型 计算
boolean c = (f ? 0 : 1)
byte char short 或int c = (int)f
long c = (int)(f ^ (F >>> 32))
float c = Float.floatToIntBits(f)
double long l = Double.doubleToLongBits(f) || c = (int)(l ^ (l >>> 32))
Object,其equals() 调用这个域的equals() c = f.hashCode()
数组 对每个元素进行上述操作

3.合并计算得到的散列码 :result = 37 * result + c;
4. 返回result
5. 检查hashCode() 最后生成的结果,确保相同的对象具有相同的散列码

下面就是一个遵循这些条件的一个例子:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CountedString {
    private static List<String> created = new ArrayList<>();
    private String s = "";
    private int id = 0;

    public CountedString(String str){
        s = str;
        created.add(s);

        for(String string : created)
            if(string.equals(s))
                id++;
    }
    @Override
    public int hashCode() {
        int result = 17;

        result = 37 * result + s.hashCode();
        result = 37 * result + id;
        return result;
    }
    @Override
    public String toString() {
        return "CountedString{" + "s='" + s + '\'' + ", id=" + id + ", hashCode=" + hashCode() + '}';
    }
    @Override
    public boolean equals(Object obj) {
        return obj instanceof CountedString &&
                s.equals(((CountedString)obj).s) &&
                id == ((CountedString)obj).id;
    }

    public static void main(String[] args) {

        Map<CountedString,Integer> map = new HashMap<>();
        CountedString[] cs = new CountedString[5];

        for (int i = 0; i < cs.length; i++) {
            cs[i] = new CountedString("Hi");
            map.put(cs[i], i);
        }

        for(CountedString countedString : cs){
            System.out.println("Looking up" + countedString);
            System.out.println(map.get(countedString));
        }
    }
}

输出:

Looking upCountedString{s='Hi', id=1, hashCode=109743}
0
Looking upCountedString{s='Hi', id=2, hashCode=109744}
1
Looking upCountedString{s='Hi', id=3, hashCode=109745}
2
Looking upCountedString{s='Hi', id=4, hashCode=109746}
3
Looking upCountedString{s='Hi', id=5, hashCode=109747}
4

CountedString 由一个String 和一个 id 组成,此id 代表包含相同String 的 CountedString对象的编号。所有的String 都被保存在一个静态属性 ArrayList 中,在构造器中通过迭代遍历此ArrayList 完成对 id 的计算。

hashCode() 与 equals() 方法都基于CountedString 的这两个域来计算结果;如果它们只基于String 或者只基于id ,不同的对象就可能会产生相同的散列码。

在main() 方法中,我们使用相同的String 创建了多个CountedString 对象。这说明虽然String 相同,但是由于id 不同,所以它们产生的散列码也不会相同。

参考书籍:
《Java 编程思想》Bruce Eckel 著 陈昊鹏 译

相关标签: hashcode