根据阿里《Java开发手册》,对 Java 对象的 hashCode
和 equals
方法,有如下强制约定。
[强制] 关于 hashCode
和 equals
的处理,遵循如下规则
1)只要覆写 equals,就必须覆写 hashCode。
2)因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须覆写这两个方法。
3)如果自定义对象作为 Map 的键,那么必须覆写 hashCode 和 equals。
说明:String 已经覆写 hashCode 和 equals 方法,所以我们可以愉快地使用 String 对象作为 key 来使用。
下面进行必要的补充分析。
equals
保证可靠性, hashCode
保证性能。
equals
和 hashCode
都可用来判断两个对象是否相等,但是二者有区别
equals
可以保证比较对象是否是绝对相等,即「 equals
保证可靠性」hashCode
用来在最快的时间内判断两个对象是否相等,可能有「误判」,即「 hashCode
保证性能」equals
为 true 时,要求 hashCode
也必须相等hashCode
为 true 时, equals
可以不等(如发生哈希碰撞时)hashCode
的「误判」指的是
hashCode
一定相等。hashCode
也可能相等,这是因为 hashCode
是根据地址 hash
出来的一个 int 32
位的整型数字,相等是在所难免。此处以向 HashMap 中插入数据(调用 put
方法, put
方法会调用内部的 putVal
方法)为例,对「 equals
保证可靠性, hashCode
保证性能」这句话加以说明, putVal
方法中,判断两个 Key 是否相同的代码如下所示。
// putVal 方法 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) ... 复制代码
在判断两个 Key 是否相同时,
hash
(通过 hashCode
的高 16 位和低 16 位进行异或运算得出)。这可以在最快的时间内判断两个对象是否相等,保证性能。hashCode
也可能相等。所以对满足 p.hash == hash
的条件,需要进一步判断。==
判断是否绝对相等, equals
判断是否客观相等。class Dog { String color; public Dog(String s) { color = s; } } public class SetAndHashCode { public static void main(String[] args) { HashSetdogSet = new HashSet (); dogSet.add(new Dog("white")); dogSet.add(new Dog("white")); System.out.println("We have " + dogSet.size() + " white dogs!"); if (dogSet.contains(new Dog("white"))) { System.out.println("We have a white dog!"); } else { System.out.println("No white dog!"); } } } 复制代码
运行程序,输出结果如下。
We have 2 white dogs! No white dog! 复制代码
根据阿里《Java开发手册》可知,「因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须覆写这两个方法」。将 Dog
代码修改为如下。
class Dog { String color; public Dog(String s) { color = s; } //重写equals方法, 最佳实践就是如下这种判断顺序: public boolean equals(Object obj) { if (!(obj instanceof Dog)) return false; if (obj == this) return true; return this.color == ((Dog) obj).color; } public int hashCode() { return color.length();//简单原则 } } 复制代码
此时,再运行程序,输出结果如下。
We have 1 white dogs! We have a white dog! 复制代码
如下代码,自定义 KeylessEntry
对象,作为 Map 的键。
class KeylessEntry { static class Key { Integer id; Key(Integer id) { this.id = id; } @Override public int hashCode() { return id.hashCode(); } } public static void main(String[] args) { Map m = new HashMap(); while (true){ for (int i = 0; i < 10000; i++){ if (!m.containsKey(new Key(i))){ m.put(new Key(i), "Number:" + i); } } System.out.println("m.size()=" + m.size()); } } } 复制代码
上述代码中,使用 containsKey(keyElement)
判断 Map 是否已经包含 keyElement
键值。 containsKey
的关键代码如下所示,使用了 hashCode
和 equals
方法进行判断。
if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))) ... 复制代码
执行上述代码,因没有重写 hashCode
和 equals
方法,导致 m.containsKey(new Key(i))
判断总是 false,导致程序不断向 Map 中插入新的 key-value
,造成死循环,最终将导致内存溢出。