深入理解 hashCode

hashCode 方法

Object 中的一个 native 方法,通过对象地址计算出该对象的哈希值(int 数值)

hashCode 的常规协定:

  • 在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。

  • 如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。

  • 以下情况不是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。(因为每次插入元素需要和桶中所有元素进行比较,因此尽量减少桶中元素,让不同的对象生成不同的哈希值,根据哈希值到数组索引的映射规则,均匀得将元素分布不同的桶中,不会造成某一桶中元素过多或过少)

  • 实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)

  • 当equals方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

  1. 同⼀个对象(已重写hashCode)多次调用 hashCode() 方法返回的哈希值应该是相同的

    什么是同一对象?根据 equals() 判断为 true 的两个对象才是同一对象。

  2. 同一类型的不同对象,哈希值应该是不同的。不同类型的对象,哈希值可能相同,如:

    String string = new String("abc");
    Integer integer = Integer.valueOf(string.hashCode());
    System.out.println(string.hashCode()==integer.hashCode());	//true
    
  3. 同一类型的不同对象,哈希值不同,但在哈希表中的索引可能相同(即在同一个桶中)

    可以将简单(实际更为复杂)将哈希值到表中索引的映射规则理解为取模运算:

    index = hashCode % arrayLength
    

    假设数组长度为8,那么hashCode=1hashCode=9的对象,在哈希表中的索引相同,即在同一个桶中

    注意:要区分哈希值与数组索引,虽然哈希值决定数组索引,但哈希值不等同于数组索引

  4. hashCode 用来判断元素在哪个桶,而 equals 用来判断是桶中哪一个元素

    倘若在重写 equals 方法时,没有重写 hashCode,该类则会继承 Object 父类的 hashCode 方法(根据对象地址来计算哈希值)。通过 equals 判断为 true 的两个对象可能会因为对象地址不同,导致计算出的哈希值不同,进而导致计算出的索引大概率不同(不能说绝对不同,见条目3),也就是通过 equals 方法比较为 true 的两个对象有很大可能在不同的桶中。

    那么此时 HashSet 和 HashMap 的不重复特性便会丢失。为什么这么说呢?因为 HashMap 的不重复特性是基于在每次向某个桶中添加元素时,使用 equals 方法对桶中元素遍历一遍,判断元素是否存在。

    也就是说,只会对根据哈希值计算出目标位置的桶中的元素来进行 equals 判断。倘若相同的两个对象在不同的桶中呢?这便造成了 Set 或 Map 中出现元素重复的现象。

    class Father implements Serializable {
        String name;
        public Father(String name) {
            this.name = name;
        }
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Father father = (Father) o;
            return Objects.equals(name, father.name);
        }
        @Override
        public String toString() {
            return "Father{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            Father yang1 = new Father("yang");
            Father yang2 = new Father("yang");
            System.out.println(yang1.equals(yang2));	//true
            //对象地址不同
            System.out.println(yang1==yang2);	//false
            //根据对象地址计算的hashCode当然也不同了
            System.out.println(yang1.hashCode()==yang2.hashCode()); //false
    
            List<Father> list = new ArrayList<>();
            list.add(yang1);
            list.add(yang2);
            System.out.println(list);	//[Father{name='yang'}, Father{name='yang'}]
        }
    }
    

    因此在重写 equals 方法之前要重写 hashCode 方法,将两个 equals 判断 true 的对象映射到同一个桶的索引上。这就是为什么在上面的常规协定中规定:如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。

    注意:重写hashCode方法时,调用的是Objects中的hash(Object… obj)方法,实际调用Arrays.hashCode(Object[] objs)方法,作用是将传入的参数对象的hashCode()方法进行逻辑运算。因此,参数对象的hashCode方法必须已经重写。

总结
  • 如果equals为true,hashcode一定相等(见条目4,两个相等的对象一定要在同一个桶中);

  • 如果hashcode值不等,equals一定不等(逆反成立);

  • 如果equals为false,hashcode不一定不相等;

  • 如果hashcode值相等,equals不一定相等;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值