java-----hashCode和equals的区别

来源:互联网 发布:知乎那些曾经轰动一时 编辑:程序博客网 时间:2024/06/06 01:50

        首先来说说两者的区别,接着给出得出这些区别的原因,最后从HashMap和HashSet实现的角度谈谈这两个集合类对hashCode和equals的使用,其实说白了,个人认为这两个区别也只是在HashMap和HashSet中体现的比较明显点;

        两者的关系:

        (1):两个对象如果equals,那么他们的hashCode也相等
        (2):两个对象如果hashCode相等,但他们不一定equals
        (3):两个对象hashCode值不等,他们一定不equals
        (4):两个对象不equals,他们的hashCode值不一定不等

        也就是说我们在判断两个对象等不等的时候,首先判断两者的hashCode值等不等,不等的话两个对象直接就不等了,相等的话再去看equals,这点我们可以从HashMap的使用中体现出来;

        首先我们通过实例来具体看下这两者的区别:

        首先来印证结论(2):

public class Test {@Overridepublic int hashCode() {System.out.println("执行了hashCode方法");return 1;}@Overridepublic boolean equals(Object obj) {System.out.println("执行了equals方法");return false;}public static void main(String[] args) {HashMap<Test, String> map = new HashMap<>();Test test1 = new Test();Test test2 = new Test();map.put(test1, "test1");map.put(test2, "test2");System.out.println("map的长度:  "+map.size());}}
        在此之前我们有必要看看HashMap的put方法实现原理了,查看源码如下:

 public V put(K key, V value) {        if (table == EMPTY_TABLE) {            inflateTable(threshold);        }        if (key == null)            return putForNullKey(value);        int hash = hash(key);        int i = indexFor(hash, table.length);        for (Entry<K,V> e = table[i]; e != null; e = e.next) {            Object k;            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {                V oldValue = e.value;                e.value = value;                e.recordAccess(this);                return oldValue;            }        }        modCount++;        addEntry(hash, key, value, i);        return null;    }
        put方法的实现过程是首先调用hash(key)计算出当前key对应的hash值,在hash(key)方法中会执行当前key对象的hashCode方法,在计算出hash值之后,会调用indexFor算出当前hash值对应要存储到的数组的下标位置,因为HashMap是数组加链表实现的,数组部分就是通过我们key的hash值来对应位置的,而链表部分存储的是hash值相同的所有键值对,indexFor的目的就是找到当前hash值对应的数组下标,有了这个下标之后你会看到一个for循环,他会遍历当前数组下标里面的链表,通过调用key的equals方法来判断当前链表中是否有与将要插入的键值对的key值相同的元素,没有的话,会将当前当前键值对插入到这个链表的表头,如果有的话,则会替换掉原先已经存在的值;   

        可以看到,put方法首先调用的是hashCode,随后在找到对应hash值在数组中的存储位置之后才会执行equals方法找链表中有没有将要插入的键值对的key值的,为了方便,我们可以这样理解,hashCode找对应hash值在数组中的位置,equals找当前key值在该数组位置相应链表中位置的,直观点就是下面这幅图;

                     

         在测试中我们将Test类作为了HashMap的key值,随后调用了HashMap的put方法,接下来我们看看输出结果:

执行了hashCode方法执行了hashCode方法执行了equals方法map的长度:  2
        第一行的输出是调用map.put(test1, "test1");执行的,第二行的输出是调用map.put(test2, "test2");执行的,因为上面测试中我们让Test类的hashCode方法的返回值都是1,那么此时所有put进map的key的hash值都是相同的,此时在执行map.put(test2, "test2");的时候就需要调用equals方法来查看当前hash值对应的数组位置的链表中有没有与当前key equals的键值对存在,因为我们在Test类中令equals的返回值是false,所以肯定就不存在了,因此map的长度为2;

        我们修改上面的测试代码,将Test类中的equals方法的返回值修改为true,也就是修改成如下代码:

public class Test {@Overridepublic int hashCode() {System.out.println("执行了hashCode方法");return 1;}@Overridepublic boolean equals(Object obj) {System.out.println("执行了equals方法");return true;}public static void main(String[] args) {HashMap<Test, String> map = new HashMap<>();Test test1 = new Test();Test test2 = new Test();map.put(test1, "test1");map.put(test2, "test2");System.out.println("map的长度:  "+map.size());}}
        查看输出:

执行了hashCode方法执行了hashCode方法执行了equals方法map的长度:  1
        不同于上面的测试,这里的map大小变成了1,原因在于我们将equals返回值设置成了true,因为两次put操作hashCode方法返回值是相同的,所以他会执行equals查看当前hash值对应的数组位置处的链表中是否存在于当前key equals的键值对,因为equals始终返回true,那么就是存在了,根据上面对put源码的分析,当前最新的值将替换掉原先的值,不信你可以试试输出key值等于test1或者test2的值,结果都将是"test2";

        如果我们把测试代码修改成下面这样:

public class Test {public static int count = 0;@Overridepublic int hashCode() {System.out.println("执行了hashCode方法");count++;return count;}@Overridepublic boolean equals(Object obj) {System.out.println("执行了equals方法");return true;}public static void main(String[] args) {HashMap<Test, String> map = new HashMap<>();Test test1 = new Test();Test test2 = new Test();map.put(test1, "test1");map.put(test2, "test2");System.out.println("map的长度:  "+map.size());}}
        查看输出:

执行了hashCode方法执行了hashCode方法map的长度:  2
        你会发现根本连equals方法都不会执行,原因在于我们让Test的hashCode方法每次都返回的值都是不一样的,这样两次put方法执行之后key值对应的hash值根本就不相等,也就是他们根本就是存储在数组中的不同下标处了,不会存储到同一下标处当然不用equals方法来查看到底是存储到下标对应处链表的哪个位置了;

        从上面的三个测试可以看出来,HashMap在put时候首先调用的是hashCode方法,如果发现当前hash值对应的数组位置处的链表为空的话是不会执行equals的,也就是hashCode方法先于equals方法执行;


       

1 1