`

为什么覆写equals的时候一定要覆写hashCode?

阅读更多
转自http://blog.csdn.net/michaellufhl/archive/2010/08/23/5833188.aspx

经常在论坛上面看到覆写hashCode函数的问题,很多情况下是一些开发者不了解hash code,或者和equals一起用的时候不太清楚为啥一定要覆写hashCode。

    对于hash code的理论我不想多说,这个话题太大。那些“对称性”,“传递性”的规则网上有无数的文章来描述。我只想说用hash code的原因只有一个:效率。理论的说法它的复杂度只有O(1)。试想我们把元素放在线性表里面,每次要找一个元素必须从头一个一个的找它的复杂度有O(n)。如果放在平衡二叉树,复杂度也有O(log n)。

   为啥很多地方说“覆写equals的时候一定要覆写hashCode”。说到这里我知道很多人知道有个原则:如果a.equals(b)那么要确保a.hashCode()==b.hashCode()。为什么?hashCode和我写的程序的业务逻辑毫无关系,为啥我要override? 要我说如果你的class永远不可能放在hash code为基础的容器内,不必劳神,您真的不必override hashCode()

    说得准确一点放在HashMap和Hashtable里面如果是作为value而不是作为key的话也是不必override hashCode了。至于HashSet,实际上它只是忽略value的HashMap,每次HashSet.add(o)其实就是HashMap.put(o, dummyObject)。

    那为什么放到Hash容器里面要overide hashCode呢?因为每次get的时候HashMap既要看equals是不是true也要看hash code是不是一致,put的时候也是要看equals和hash code。

    如果说到这里您还是不太明白,咱就举个例子:

    譬如把一个自己定义的class Foo{...}作为key放到HashMap。实际上HashMap也是把数据存在一个数组里面,所以在put函数里面,HashMap会调Foo.hashCode()算出作为这个元素在数组里面的下标,然后把key和value封装成一个对象放到数组。等一下,万一2个对象算出来的hash code一样怎么办?会不会冲掉?先回答第2个问题,会不会冲掉就要看Foo.equals()了,如果equals()也是true那就要冲掉了。万一是false,就是所谓的collision了。当2个元素hashCode一样但是equals为false的时候,那个HashMap里面的数组的这个元素就变成了链表。也就是hash code一样的元素在一个链表里面,链表的头在那个数组里面。

回过来说get的时候,HashMap也先调key.hashCode()算出数组下标,然后看equals如果是true就是找到了,所以就涉及了equals。

    假设如果有个key为a的元素在HashMap里面的情况:

    1:如果这时候用equals为true但是hashCode不等的b作为get参数的话,这个时候b算出来的数组下标一定不是a所在的下标位置。

    2:如果这时候用equals为false但是hashCode相等的b作为get参数的话,这个时候b算出来的数组下标是对了,但是用equals来寻找相符的key就找不到a了。

    以上2种情况要么就是get找不到符合的元素返回null,要么就是返回一个hashCode和equals恰好都符合b的另外的元素,这就产生了混乱。混乱的根本就是错误实现hashCode和equals。

  下面有个非常简化版的HashMap实现帮助大家理解,您甚至可以把它当作伪代码来看。(这个实现只是为了模拟HashMap的机制,所以参数检查,访问修饰符都忽略。在java.util.HashMap里面的对hash值的二次hash,根据数组长度计算index,以及超出数组时的resize都忽略)

view plaincopy to clipboardprint?
/*  
 * Just to demonstrate hash map mechanism,   
 * Please do not use it in your commercial product.  
 *   
 * Argument checking and modifier are ignored.  
 * In java.util.HashMap, array are used instead of ArrayList, so index of array is calculated by 'h & (length-1)',  
 * while we use ArrayList to skip the calculation for simple.  
 *   
 * @author Shengyuan Lu 卢声远 <michaellufhl@yahoo.com.cn>  
 */  
public class SimpleHashMap {   
    ArrayList<LinkedList<Entry>> entries = new ArrayList<LinkedList<Entry>>();   
       
    /**  
     * Each key-value is encapsulated by Entry.  
     */  
    static class Entry {   
        Object key;   
        Object value;   
        public Entry(Object key, Object value) {   
            this.key = key;   
            this.value = value;   
        }   
    }   
    void put(Object key, Object value) {   
        LinkedList<Entry> e = entries.get(key.hashCode());   
        if (e != null) {   
            for (Entry entry : e) {   
                if (entry.key.equals(key)) {   
                    entry.value = value;// Match in lined list   
                    return;   
                }   
            }   
            e.addFirst(new Entry(key, value));// Add the entry to the list   
        } else {   
            // Put the new entry in array   
            LinkedList<Entry> newEntry = new LinkedList<Entry>();   
            newEntry.add(new Entry(key, value));   
            entries.add(key.hashCode(), newEntry);   
        }   
    }   
    Object get(Object key) {   
        LinkedList<Entry> e = entries.get(key.hashCode());   
        if (e != null) {   
            for (Entry entry : e) {   
                if (entry.key.equals(key)) {   
                    return entry.value;   
                }   
            }   
        }   
        return null;   
    }   
       
    /**  
     * Do we need to override equals() and hashCode() for SimpleHashMap itself?   
     * I don't know either:)  
     */  
}  



这个问题的权威阐释可以参考Bloch的<Effective Java>的 Item 9: Always override hashCode when you override equals




分享到:
评论

相关推荐

    Java重写equals及hashcode方法流程解析

    "Java重写equals及hashcode方法流程解析" Java中的equals和hashCode方法是两个非常重要的方法,它们都是Object类中的方法。在实际开发中,正确地重写这两个方法对于确保程序的正确性和性能至关重要。下面,我们将...

    Effective Java第三版1

    可能涵盖Java语言的发展历程,以及为什么要遵循有效的编程习惯来提升代码质量。 ### 第二章 创建和销毁对象 1. **考虑用静态工厂方法替换构造器**:静态工厂方法比构造器更灵活,它们可以返回已存在或新创建的对象...

    高级开发人员面试宝典之各种面试题以及点评.docx

    * 覆写 hashcode 方法,需要把某个非零常数值,例如 17,保存在 int 变量 result 中。 * 对于对象中每一个关键域 f(指 equals 方法中考虑的每一个域): + 对于 boolean 型,计算(f? 0 : 1)。 + 对于 byte,char,...

    Java 编程军规.docx

    #### 军规八:覆写equals()方法时同时覆写hashCode() - **解释**:当一个对象的equals()方法被覆写后,如果没有同时覆写hashCode()方法,可能会导致该对象在哈希表中的行为不符合预期。 - **实践建议**:确保equals...

    Java开发军规.docx

    当覆写对象的equals()方法时必须同时覆写hashCode()方法。equals和hashCode方法是对象在hash容器内高效工作的基础,正确的覆写这两个方法才能保证在hash容器内查找对象的正确性。 遵循这些军规可以确保Java开发的...

    提高代码质量的方法.

    - 覆写equals方法时要处理null值,同时覆写hashcode以保持一致性。 - 推荐覆写toString方法,提供更友好的输出。 - 使用package-info.java为包提供元数据。 4. **字符串操作**: - 使用字符串直接量赋值,提高...

    Java2全方位讲议(内部讲议)9

    2. **覆写 hashCode 方法**:为了确保相等的对象有相同的哈希码,你需要在重写 `equals` 方法的同时,也重写 `hashCode` 方法。可以参考包装类(Wrapper Classes)中的实现。 **对象的复制** 1. **Object 类的 ...

    JAVA程序员面试题(含有答案)经典版

    为什么要有 GC? GC 是垃圾收集,使用 GC 可以进行垃圾空间的释放操作。 10. String s=new String(“xyz”);创建了几个 String Object? 产生两个实例化对象,一个是“xyx”,一个是指向“xyx”的引用对象。 11....

    Java程序的三十个基本规则

    23. **使用equals()与hashCode()**:正确覆写equals()和hashCode()方法,确保对象比较的一致性。 24. **代码复用**:合理使用方法重载和方法抽取,避免代码冗余。 25. **类型检查**:在必要时进行类型转换,避免...

    [计算机软件及应用]Java开发规范.doc

    避免使用Java内置关键字或具有特殊含义的方法名,例如`equals`、`hashCode`、`clone`、`finalize`等,除非你确实需要覆写这些方法。 **2.1.3 Java Bean的boolean属性** 对于Java Bean中的boolean属性,访问器方法应...

    华为java编程军规

    军规八:覆写对象的 equals() 方法时必须同时覆写 hashCode() 方法。这是因为当对象被添加到集合中时,需要使用 hashCode() 方法来判断对象的唯一性。 军规九:禁止循环中创建新线程,尽量使用线程池。这是因为循环...

    通过Lombok来简化你的代码1

    Lombok是一款java库,主要用来简化java代码的编写,通过使用注解,可以减少大量的getter、setter、equals、hashCode、toString等方法的编写,从而提高开发效率。 安装Lombok 要使用Lombok,需要先安装Lombok插件。...

    java学习笔记(二)

    覆写`equals`方法是为了比较两个对象是否相等,通常在重写`equals`时,也需要考虑覆写`hashCode`方法以保持一致性。 总的来说,本篇笔记涵盖了Java面向对象编程的基础知识,包括类和对象的定义、方法的创建、对象的...

    java集合相关学习.pdf

    为了优化Map的性能,对象在作为键使用时,通常需要覆写hashCode()和equals()方法,确保当两个键被视为相等时,它们的哈希码也相同。 总结,Java集合框架为开发者提供了灵活的数据组织方式,无论是在简单的数据管理...

    资深程序员的Java面试题总结汇总.pdf

    两个对象的`hashCode()`相同,表示它们可能位于相同的哈希桶中,但不保证`equals()`一定为true,除非该类正确重写了`equals()`和`hashCode()`方法。 4. `final`关键字用于声明常量或不可改变的变量,也可以用于修饰...

    Java集合Collection、List、Set、Map使用详解

    **为什么要使用容器?** - **动态调整大小**:容器可以根据需要动态扩展或缩小,不像数组那样需要预先指定大小。 - **消除重复元素**:某些类型的容器(如`Set`)能够确保其中的元素不重复。 - **键值对映射**:`Map...

    java集合详解

    为了在HashMap中高效地定位键值对,键对象需要覆写hashCode()方法,确保不同的键返回不同的哈希值,减少冲突。 1.5 SET Set接口继承自Collection,不允许重复元素,如HashSet和TreeSet是其典型实现。Set接口的实现...

    2018java面试宝典

    默认情况下,equals 方法的行为类似于 "==",但可以被覆写以实现更复杂的比较逻辑。 - **示例代码**: ```java Integer a = new Integer(10); Integer b = new Integer(10); if (a == b) { System.out.println...

Global site tag (gtag.js) - Google Analytics