覆写equals方法必须覆写hashCode方法,是JDK API上反复说明的,不过为什么要这样做呢?这两个方法之间有什么关系呢?
void test() { // Person类的实例作为Map的key Map<Person, Object> map = new HashMap<Person, Object>(); map.put(new Person("张三"), new Object()); // Person类的实例作为List的元素 List<Person> list = newArrayList<Person>() list.add(new Person("张三")); // 列表中是否包含 boolean b1 = list.contains(new Person("张三")); // Map中是否包含 boolean b2 = map.containsKey(new Person("张三")); System.out.println(b1 + "|" + b2); }
Person类:
public class Person { private String name; private String age; public Person(String name) { setName(name); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } @Override public boolean equals(Object o) { /* * 使用getClass是为了防止子类被判断为父类的情况 * 例如:Person中覆写equals方法 {return name.equals(p.getName);} * 子类Employee继承Person,此时子类中的euqals方法super.equlas(p); * 此时如果使用instanceof关键字,因为Employee是Person的子类,所以返回true * 进一步调用equals返回的是true,所以会将Person和Employee认为是同一人,显然不合理 */ if(o != null && o.getClass() == this.getClass()) { Person p = (Person) o; if(p.getName() == null && this.name == null) return false; return this.name.equals(p.getName()); } return false; } }
我们先来看b1,Person类的equals覆写了,不再判断两个地址是否相等,而是根据人员的姓名来判断两个对象是否相等,所以不管我们的 new Person(“张三”)产生了多少个对象,它们都是相等的。把“张三”对象放入List中,再检查List中是否包含,那结果肯定是true了。
接着来看b2,我们把张三这个对象作为了Map的键(Key),放进去的对象是张三,检查的对象还是张三,那应该和List的结果相同了,但是很遗憾,结果是false。原因何在呢?
原因就是HashMap的底层处理机制是以数组的方式保存Map条目(Map Entry)的,这其中的关键是这个数组下标的处理机制:依据传入元素hashCode方法的返回值决定其数组的下标,如果该数组位置上已经有了Map条目,且与传入的键值相等则不处理,若不相等则覆盖;如果数组位置没有条目,则插入,并加入到Map条目的链表中。同理,检查键是否存在也是根据哈希码确定位置,然后遍历查找键值的。
接着深入探讨,那对象元素的hashCode方法返回的是什么值呢?它是一个对象的哈希码,是由Object类的本地方法生成的,确保每个对象有一个哈希码(这也是哈希算法的基本要求:任意输入k,通过一定算法f(k),将其转换为非可逆的输出,对于两个输入k1和k2,要求若k1=k2,则必须 f(k1)=f(k2),但也允许k1≠k2,f(k1)=f(k2)的情况存在)。
那回到我们的例子上,由于我们没有重写hashCode方法,两个张三对象的hashCode方法返回值(也就是哈希码)肯定是不相同的了,在HashMap的数组中也就找不到对应的Map条目了,于是就返回了false。
问题清楚了,修改也非常简单,重写一下hashCode方法即可,代码如下:
public int hashCode() { //其中HashCodeBuilder是org.apache.commons.lang.builder包下的一个哈希码生成工具, //使用起来非常方便,诸位可以直接在项目中集成。 return new HashCodeBuilder().append(name).toHashCode(); }
其中HashCodeBuilder是org.apache.commons.lang.builder包下的一个哈希码生成工具,使用起来非常方便,诸位可以直接在项目中集成。(为什么不直接写hashCode方法?因为哈希码的生成有很多种算法,自己写麻烦,事儿又多,所以采用拿来主义是最好的方法。)