对Java中HashCode方法的深入思考( 三 )

(obj) >> 3 ; value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ; } else if (hashCode == 2) { value = 1 ; // for sensitivity testing } else if (hashCode == 3) { value = ++GVars.hcSequence ; } else if (hashCode == 4) { value = cast_from_oop(obj) ; } else { // Marsaglia's xor-shift scheme with thread-specific state // This is probably the best overall implementation -- we'll // likely make this the default in future releases. unsigned t = Self->_hashStateX ; t ^= (t << 11) ; Self->_hashStateX = Self->_hashStateY ; Self->_hashStateY = Self->_hashStateZ ; Self->_hashStateZ = Self->_hashStateW ; unsigned v = Self->_hashStateW ; v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ; Self->_hashStateW = v ; value = v ; } value &= markOopDesc::hash_mask; if (value == 0) value = 0xBAD ; assert (value != markOopDesc::no_hash, "invariant") ; TEVENT (hashCode: GENERATE) ; return value;}复制代码从get_next_hash的方法中我们可以看到,如果从0开始算的话,这里提供了6种计算 hash 值的方案,有自增序列,随机数,关联内存地址等多种方式,其中官方默认的是最后一种,即随机数生成 。可以看出 hashCode 也许和内存地址有关系,但不是直接代表内存地址的,具体需要看虚拟机版本和设置 。
equals和hashCodeequals 和 hashCode 都是 Object 类拥有的方法,包括 Object 类中的 toString 方法打印的内容也包含 hashCode 的无符号十六进制值 。
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode());}复制代码由于需要比较对象内容,所以我们通常会重写 equals 方法,但是重写 equals 方法的同时也需要重写 hashCode 方法,有没有想过为什么?
因为如果不这样做的话,就会违反 hashCode 的通用约定,从而导致该类无法结合所有基于散列的集合一起正常工作,这类集合包括 HashMap 和 HashSet 。
这里的通用约定,从 Object 类的 hashCode 方法的注释可以了解,主要包括以下几个方面,

  • 在应用程序的执行期间,只要对象的 equals 方法的比较操作所用到的信息没有被修改,那么对同一个对象的多次调用,hashCode 方法都必须始终返回同一个值 。
  • 如果两个对象根据 equals 方法比较是相等的,那么调用这两个对象中的 hashCode 方法都必须产生同样的整数结果 。
  • 如果两个对象根据 equals 方法比较是不相等的,那么调用者两个对象中的 hashCode 方法,则不一定要求 hashCode 方法必须产生不同的结果 。但是给不相等的对象产生不同的整数散列值,是有可能提高散列表(hash table)的性能 。
从理论上来说如果重写了 equals 方法而没有重写 hashCode 方法则违背了上述约定的第二条,相等的对象必须拥有相等的散列值 。
但是规则是大家默契的约定,如果我们就喜欢不走寻常路,在重写了 equals 方法后没有覆盖 hashCode 方法,会产生什么后果吗?
我们自定义一个 Student 类,并且重写了 equals 方法,但是我们没有重写 hashCode 方法,那么当调用 Student 类的 hashCode 方法的时候,默认就是调用超类 Object 的 hashCode 方法,根据随机数返回的一个整型值 。
public class Student { private String name; private String gender; public Student(String name, String gender) { this.name = name; this.gender = gender; } //省略 Setter,Gettter@Override public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof Student) { Student anotherStudent = (Student) anObject; if (this.getName() == anotherStudent.getName() || this.getGender() == anotherStudent.getGender()) return true; } return false; }}复制代码我们创建两个对象并且设置属性值一样,测试下结果:
public static void main(String[] args) { Student student1 = new Student("小明", "male"); Student student2 = new Student("小明", "male"); System.out.println("equals结果:" + student1.equals(student2)); System.out.println("对象1的散列值:" + student1.hashCode() + ",对象2的散列值:" + student2.hashCode());}复制代码得到的结果
【对Java中HashCode方法的深入思考】equals结果:true对象1的散列值:1058025095,对象2的散列值:665576141复制代码我们重写了 equals 方法,根据姓名和性别的属性来判断对象的内容是否相等,但是 hashCode 由于是调用 Object 类的 hashCode 方法,所以打印的是两个不相等的整型值 。
如果这个对象我们用 HashMap 存储,将对象作为 key,熟知 HashMap 原理的同学应该知道,HashMap 是由数组 + 链表的结构组成,这样的结果就是因为它们 hashCode 不相等,所以放在了数组的不同下标,当我们根据 Key 去查询的时候结果就为 null 。


推荐阅读