为什么重写equals()时,必须重写hashCode()

hashCode()方法和equals()方法的作用其实一样,都是用来比较两个对象是否相等,既然equals()方法已经能实现对比的功能,为什么还要用hashCode()呢?


首先我们来看Object类

java.lang.Object类中有两个非常重要的方法:

    public  int hashCode();
    public boolean equals(Object obj);

Object类是所有类的父类,所有的对象,包括数组,都实现了在Object类中定义的方法

equals()方法详解

equals()方法使用来判断当前对象和其它对象是否相等

源码

public boolean equals(Object obj) {
    
    
        return (this == obj);
    }

很明显是对两个对象的地址进行比较(引用是否相同)。但是,String、和包装类在使用equals()方法时,已经覆盖了
Object类的equals()方法。

为什么重写equals()方法时必须重写hashCode()方法

我们用一个案例演示一下,

1.新建一个Person 类中没有重写equals()方法和hashCode()方法
public class Person {
    
    
    private int age;
    private String name;

    public Person(String name, int age) {
    
    
        this.name = name;
        this.age = age;
    }
}

测试类

package com.blog.controller;

import java.util.HashMap;
import java.util.Map;

public class Test {
    
    
    public static void main(String[] args) {
    
    
        Person p1 = new Person("张三", 18);
        Person p2 = new Person("张三", 18);
        System.out.println("equals()比较:"+p1.equals(p2));
        System.out.println("p1---hashCode:"+p1.hashCode());
        System.out.println("p2---hashCode:"+p2.hashCode());
       
        Map<Person, String> map = new HashMap<>();
        map.put(p1, "zhangsan");
        System.out.println("结果是:" + map.get(p2));

    }
}

输出结果:

equals()比较:false
p1---hashCode:1995265320
p2---hashCode:746292446
结果是:null

执行main方法发现,运行equals()方法的执行结果为false;

分析:
Pserson类没有覆盖equals()方法,p1调用的equals()方法实际上等同于调用Object类的equals()方法。比较的是对象的内存地址是否相等。因为两个新对象所以内存地址不同,则p1.equals(p2)

2.只重写hashCode()方法而不重写equals()方法时
public class Person {
    
    
    private int age;
    private String name;

    public Person(String name, int age) {
    
    
        this.name = name;
        this.age = age;
    }

    @Override public int hashCode() {
    
    
        int result = name.hashCode();
        result = 31 * result + age;
        return result;
    }

}

输出结果:

equals()比较:false
p1---hashCode:24021577
p2---hashCode:24021577
结果是:null

分析:
hash码是age和name生成的,我们没有重写equals方法只重写了hashCode()方法,两个对象的hashCode值一样,但通过map.get(p2)获取值却null,因为在hashMap.get()方法中会进行equals()方法进行比较两个对象是否为false,所以就没有比较两个对象的hashCode值

##3.重写equals()方法不重写hashCode()方法时,让Person类通过判断对象内容是否相等类确定对象是否相等

public class Person {
    
    
    private String name;
    private int age;

    public Person(String name, int age) {
    
    
        this.name = name;
        this.age = age;
    }

    @Override public boolean equals(Object o) {
    
    
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        Person person = (Person)o;
        return age == person.age && Objects.equals(name, person.name);
    }
    
}

输出结果:

equals()比较:true
p1---hashCode:1995265320
p2---hashCode:746292446
结果是:null

分析:
因为Person两个对象的age和name属性相等,而且重写了equals方法来判断的,所以p1.equals(p2)为true,注意 :map.get(p2)时,期望结果是"zhangsan"却为null

为什么map.get(p2)时,期望结果是"zhangsan"为null

用equals比较对象相同,但是在hashMap中却以不同的对象存储(没有重写hashCode(),两个hashCode()值,在它看来就是两个对象)
到底两个对象相等不相等?
说明必须重写hashCode()的重要性

为什么会出现这种与自己期望不一样的结果呢

Person类中没有重写hashCode()方法,导致了两个相同的实例具有不相同的散列码(hashCode),违背了hashCode约定。
##4.重写equals()和hashCode()

public class Person {
    
    
    private int age;
    private String name;

    public Person(String name, int age) {
    
    
        this.name = name;
        this.age = age;
    }

    @Override public boolean equals(Object o) {
    
    
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        Person person = (Person)o;
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override public int hashCode() {
    
    
        int result = name.hashCode();
        result = 31 * result + age;
        return result;
    }

}

输出结果:

equals()比较:true
p1---hashCode:24021577  //相同的值
p2---hashCode:24021577
结果是:zhangsan  //说明以一个值key存储,相同的值

当map.get(p2)时,结果是"zhangsan"

这也就说明了重写equals()方法同时重写hashCode()方法,就是为了保证当这两个对象equals()方法比较相等时,那么hashCode值也一定要相等,equals为true,hashCode相等,所以在map.get(p2)时,结果是"zhangsan"

##5.那么我们就来做一个特别的例子吧,用hashSet添加对象,然后修改一下p2的属性值

public class Test {
    
    
    public static void main(String[] args) {
    
    
        Person p1 = new Person("张三", 18);
        Person p2 = new Person("张三", 18);
        System.out.println("equals()比较:"+p1.equals(p2));
        System.out.println("p1---hashCode:"+p1.hashCode());
        System.out.println("p2---hashCode:"+p2.hashCode());

        Set<Person> set = new HashSet<>();
        set.add(p1);
        set.add(p2);
        System.out.println("set.size():"+set.size());
        p2.setAge(20);
        System.out.println("set.remove:"+set.remove(p2));
    }
}

输出结果:

equals()比较:true
p1---hashCode:24021577
p2---hashCode:24021577
set.size():1
set.remove:false


分析:
在获取集合元素对象放入set中时,那么以后该对象的属性参与了hashCode的计算,伴儿以后就不能修改该对象参与的hashCode计算的那些属性了,否在可能会引起一些意想不到的错误

HashSet的add
public boolean add(E e) {
    
    
        return map.put(e, PRESENT)==null;
    }

map.put源码

    final Node<K,V> getNode(int hash, Object key) {
    
    
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        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 {
    
    
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

##通过案例回到上面问题:equals()方法已经能实现对比的功能,为什么还要用hashCode()呢?

  • 重写的equals方法里一般比较的比 全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只需要生成hash值进行比较就可以了,效率也很高,既然hashCode效率这么高,为哈还要重写equals()呢。

  • 因为我们的hashCode()并不是完全可靠,有时候不同的对象生成的hashCode也会一样,所以说hashCode()方法大部分时候可靠,并不是绝对可靠,由此我们可以得出:

  • equals()相等对象他们的hashCode肯定相等,也就是equals比较是绝对可靠的
  • hashCode()相等的两个对象他们的equals()不一定相等,也就是为什么说hashCode不是绝对可靠的。
  • hashCode 只要在集合中用的到

总结
1.为什么要重写equals呢?因为在java的集合中,通过equals来判断两个对象是否相等的
2.当集合元素过多时,或者是重写equals()方法比较复杂时,我们只用equals()方法进行比较判断,效率较低,所以引入hashCode这个方法,为了提高效率

我们也可以得出:

  1. equals相等的两个对象他们的hashCode肯定是相等的,也就是equals去比较对象是绝对可靠的
  2. hashCode相等的对象他们呢的equals不一定相等,这也是hashCode不是绝对可靠的。
  3. 换句话说equals不相等的对象hashCode有可能相等(可能是哈希码产生造成的冲突)

猜你喜欢

转载自blog.csdn.net/weixin_47815882/article/details/108404414