Java 集合中有两个类: List,Set
List 是有序可以重复, Set 是无序不可以重复
这样添加元素时就要判断元素是否重复
此时就要用到 object.equals()方法
但如果集合中元素太多, 效率就会很低
所以就发明了 hashCode()方法
将集合分成若干个区域, 计算每个元素的 hash 值, 根据 hash 值放入区域中
这样一来, 添加元素就要先计算元素的 hash 值, 找到对应区域
如果对应区域为空, 就存入
如果对应区域有值, 就调用 equals 方法比较, 相同则不存入, 不同则散列到其他地址
这是两个方法的源码, 我们可以看出:
hashcode 是本地方法, java 的内存是安全的, 因此无法根据散列码得到对象的内存地址, 但实际上, hashcode 是根据对象的内存地址经哈希算法得来的
而 equals 方法是严格判断一个对象是否相等的方法(object1 == object2), 比较的是两个对象的内存地址
在我们的业务系统中判断对象时有时候需要的不是一种严格意义上的相等, 而是一种业务上 (内容) 的对象相等. 在这种情况下, 原生的 equals 方法就不能满足我们的需求了, 所以这个时候我们需要重写 equals 方法, 来满足我们的业务系统上的需求.
那么为什么在重写 equals 方法的时候需要重写 hashCode 方法呢?
我们先来看一下 Object.hashCode 的通用约定(摘自《Effective Java》第 45 页)
1. 在一个应用程序执行期间, 如果一个对象的 equals 方法做比较所用到的信息没有被修改的话, 那么, 对该对象调用 hashCode 方法多次, 它必须始终如一地返回同一个整数. 在同一个应用程序的多次执行过程中, 这个整数可以不同, 即这个应用程序这次执行返回的整数与下一次执行返回的整数可以不一致.
2. 如果两个对象根据 equals(Object)方法是相等的, 那么调用这两个对象中任一个对象的 hashCode 方法必须产生同样的整数结果.
3. 如果两个对象根据 equals(Object)方法是不相等的, 那么调用这两个对象中任一个对象的 hashCode 方法, 不要求必须产生不同的整数结果. 然而, 程序员应该意识到这样的事实, 对于不相等的对象产生截然不同的整数结果, 有可能提高散列表 (hash table) 的性能.
如果只重写了 equals 方法而没有重写 hashCode 方法的话, 则会违反约定的第二条: 相等的对象必须具有相等的散列码(hashCode)
同时对于 HashSet 和 HashMap 这些基于散列值 (hash) 实现的类. HashMap 的底层处理机制是以数组的方法保存放入的数据的(Node<K,V>[] table), 其中的关键是数组下标的处理. 数组的下标是根据传入的元素 hashCode 方法的返回值再和特定的值异或决定的. 如果该数组位置上已经有放入的值了, 且传入的键值相等则不处理, 若不相等则覆盖原来的值, 如果数组位置没有条目, 则插入, 并加入到相应的链表中. 检查键是否存在也是根据 hashCode 值来确定的. 所以如果不重写 hashCode 的话, 可能导致 HashSet,HashMap 不能正常的运作,
如果我们将某个自定义对象存到 HashMap 或者 HashSet 及其类似实现类中的时候, 如果该对象的属性参与了 hashCode 的计算, 那么就不能修改该对象参数 hashCode 计算的属性了. 有可能会移除不了元素, 导致内存泄漏.
总结一下关于 hashcode 的一些规定:
两个对象相等, hashcode 一定相等
两个对象不等, hashcode 不一定不等
hashcode 相等, 两个对象不一定相等
hashcode 不等, 两个对象一定不等
扩展:
在重写 equals 方法的时候, 需要遵守下面的通用约定:
1, 自反性.
对于任意的引用值 x,x.equals(x)一定为 true.
2, 对称性.
对于任意的引用值 x 和 y, 当且仅当 y.equals(x)返回 true 时, x.equals(y)也一定返回 true.
3, 传递性.
对于任意的引用值 x,y 和 z, 如果 x.equals(y)返回 true, 并且 y.equals(z)也返回 true, 那么 x.equals(z)也一定返回 true.
4, 一致性.
对于任意的引用值 x 和 y, 如果用于 equals 比较的对象没有被修改的话, 那么, 对此调用 x.equals(y)要么一致地返回 true, 要么一致的返回 false.
5, 对于任意的非空引用值 x,x.equals(null)一定返回 false.
重写 hashCode 方法的大致方式:
a, 把某个非零常数值, 比如说 17(最好是素数), 保存在一个叫 result 的 int 类型的变量中.
b, 对于对象中每一个关键域 f(值 equals 方法中考虑的每一个域), 完成一些步骤:
1, 为该域计算 int 类型的散列吗 c:
1), 如果该域是 boolean 类型, 则计算(f?0:1).
2), 如果该域是 byte,char,short 或者 int 类型, 则计算(int)f.
3), 如果该域是 float 类型, 则计算 Float.floatToIntBits(f).
4), 如果该域是 long 类型, 则计算(int)(f ^ (f>>>32)).
5), 如果该域是 double 类型, 则计算 Double.doubleToLongBits(f)得到一个 long 类型的值, 然后按照步骤 4, 对该 long 型值计算散列值.
6), 如果该域是一个对象引用, 并且该类的 equals 方法通过递归调用 equals 的方式来比较这个域, 则同样对这个域递归调用 hashCode. 如果要求一个更为复杂的比较, 则为这个域计算一个 "规范表示", 然后针对这个范式表示调用 hashCode. 如果这个域的值为 null, 则返回 0(或者其他某个常数)
7), 如果该域是一个数组, 则把每一个元素当做单独的域来处理. 也就是说, 递归地应用上述规则, 对每个重要的元素计算一个散列码, 然后根据步骤下面的做法把这些散列值组合起来.
2, 按照下面的公式, 把步骤 1 中计算得到的散列码 C 组合到 result 中:
result = 31*result+c.
c, 返回 result.
可以通过 org.apache.commons.lang.builder.HashCodeBuilder 这个工具类来方便的重写 hashCode 方法.
来源: http://www.bubuko.com/infodetail-2993353.html