hashCode 方法
Object 中的一个 native 方法,通过对象地址计算出该对象的哈希值(int 数值)
hashCode 的常规协定:
在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。
以下情况不是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。(因为每次插入元素需要和桶中所有元素进行比较,因此尽量减少桶中元素,让不同的对象生成不同的哈希值,根据哈希值到数组索引的映射规则,均匀得将元素分布不同的桶中,不会造成某一桶中元素过多或过少)
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)
当equals方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
-
同⼀个对象(已重写hashCode)多次调用 hashCode() 方法返回的哈希值应该是相同的
什么是同一对象?根据 equals() 判断为 true 的两个对象才是同一对象。
-
同一类型的不同对象,哈希值应该是不同的。不同类型的对象,哈希值可能相同,如:
String string = new String("abc"); Integer integer = Integer.valueOf(string.hashCode()); System.out.println(string.hashCode()==integer.hashCode()); //true
-
同一类型的不同对象,哈希值不同,但在哈希表中的索引可能相同(即在同一个桶中)
可以将简单(实际更为复杂)将哈希值到表中索引的映射规则理解为取模运算:
index = hashCode % arrayLength
假设数组长度为8,那么
hashCode=1
和hashCode=9
的对象,在哈希表中的索引相同,即在同一个桶中注意:要区分哈希值与数组索引,虽然哈希值决定数组索引,但哈希值不等同于数组索引
-
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不一定相等;