覆盖equals时总要覆盖hashcode方法

1、这样干的原因是什么?

如果这个类仅仅是重写了equals方法而没有重写hashCode,那么这个类和基于散列的集合类一起工作时就会出现问题。这样的集合包括HashMap,HashSet以及Hashtable。

比如这种情况:

public final class PhoneNumber {
    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (!(o instanceof PhoneNumber))
            return false;
        PhoneNumber pn = (PhoneNumber) o;
        return pn.lineNumber == lineNumber
                && pn.prefix == prefix
                && pn.areaCode == areaCode;
    }
}
Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
m.put(new PhoneNumber(707, 867, 5309), "Jenny");

System.out.println(m.get(new PhoneNumber(707, 867, 5309)));

输出结果为:null    
//
这就出错了,对象相等,但是hashCode不相等,所以取不到值。



为什么会出现这种情况呢,下面我们看一看Map的源码就清楚了:

final Node<K,V> getNode(int hash, Object key) {

     Node<K,V>[] tab; 
     Node<K,V> first, e; 
     int n; 
     K k;

     /**
      * 检查table是否为空,table为HashMap实例中的一个存放数据的数组
      * 检查第一个元素的hash码是否为null
      */
     if ((tab = table) != null && (n = tab.length) > 0 &&
         (first = tab[(n - 1) & hash]) != null) {

        /*
         * 效率方面的考虑
         * 总是将第一个对象拿出来比较,如果符合要求直接返回
         * 避免执行循环准备工作的一系列代码
         */
         if (first.hash == hash && // always check first node
             ((k = first.key) == key || (key != null && key.equals(k))))
             return first;

         //进入循环之前的一系列准备以及检查工作
         if ((e = first.next) != null) {
             if (first instanceof TreeNode)
                 return ((TreeNode<K,V>)first).getTreeNode(hash, key);
             do {
                //循环内部利用&&运算符的惰性,如果hash码不相同就直接跳过了= =
                 if (e.hash == hash &&
                     ((k = e.key) == key || (key != null && key.equals(k))))
                     return e;
             } while ((e = e.next) != null);
         }
     }
     return null;
 }



put方法把电话号码对象存放在一个散列桶(hash bucket)中,get方法却在另外一个散列桶里面查找。即使这两个实例正好被放到了同一个散列桶里面,get方法也一定会返回null,因为HashMap做了一项优化,将每个项相关联的散列码存放起来,如果hash码不匹配,则不会去检验对象的等同性,充分利用了逻辑与运算的惰性(&&)。

2、规范

两个对象使用equals返回true,则它们的hashCode也一定相等;如果两个对象的hashCode相等,则它们的equals则不一定相等。

Object规范[JavaSE6]:

在应用程序的一次执行期间,只要对象的equals方法的比较操作所用的信息没有被修改,那么对这个对象调用多次,hashCode方法都必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致

如果两个对象根据equals(Object)方法比较结果相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。

如果两个对象根据equals(Object)方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法不一定要产生不一样的整数结果。但是我们应该知道,给不相等的对象产生不相同的整数结果,可以提高散列表(hashtable)的性能。



3、实现hashCode

如何实现hashCode,当然你可以使hashCode返回一个固定的数值,任何对象的hashCode都是一个固定的数值,这没有问题。但当它与基于散列的集合类一起工作时,这些元素将具有相同的散列码,进而使得所有对象都被映射到统一散列桶中,使得散列表退化为链表。具体重写hashCode的算法不在赘述。

始终要覆盖toString

为什么这么干?

原因在于在有的场景下会打印一条日志,日志的内容就是POJO类的属性字段值,这个时候toString的意义很明显的就体现出来了