Java 为什么重写equals的时候必须重写hashCode

Posted 赵晓东-Nastu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 为什么重写equals的时候必须重写hashCode相关的知识,希望对你有一定的参考价值。

一、对于包装类型的比较,使用的是equals方法 而不是==
1、首先equals是Object中的方法,Object中equals方法是怎么实现的呢。

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

2、但是对于对象来说,==比较的是对象在内存中的地址,而不是值的大小,为了能比较对应的值的大小,包装类重写了
Object中的equals方法,使之比较对应的值的大小。

案例:

public class BackTrace 
    public static void main(String[] args) 
        Object obj = new Object();
        obj.equals(obj);
        new BackTrace().f();
    
    public void f()
        Integer i1 = new Integer(1);
        Integer i2 = new Integer(1);
        Double d = new Double(1.00);

        if (i1 == i2) 
            System.out.println("A");
        
        if (i1.equals(i2)) 
            System.out.println("B");
        

        if (i1.equals(d)) 
            System.out.println("C");
        
    

我们可以看一下打印的结果是什么

为什么没有打印C呢?我们来看一下源码

    public boolean equals(Object obj) 
        if (obj instanceof Integer) 
            return value == ((Integer)obj).intValue();
        
        return false;
    

在源码中,我们会首先进行判断传进来的Obj是否属于Integer,如果不属于Integer则直接返回false,所以我们会直接返回false而不会输出C。
再接下来,我们看一下输出B,源码中给出的是intValue() 而不是比较的地址,所以会输出B

二、对自定义类型如何比较
1、肯定不能用==符号来比较
2、重写equals方法,自定义比较规则,如果一个人的姓名和年龄是相同的,那么就认为同一个人
3、如果重写equals方法,则必须重写hashCode方法(为什么?)

/**
 * @Classname Person
 * @Description 人
 * @Date 2022/1/4 14:09
 * @Author zhaoxiaodong
 */
public class Person 
    //姓名、年龄、地址
    private String name;
    private int age;
    private String address;

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

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    public int getAge() 
        return age;
    

    public void setAge(int age) 
        this.age = age;
    

    public String getAddress() 
        return address;
    

    public void setAddress(String address) 
        this.address = address;
    
    


public void f2()
        Person p1 = new Person("张三",10,"北京");
        Person p2 = new Person("张三",10,"北京");
        if (p1.equals(p2)) 
            System.out.println("Hello World");
        
    
    public static void main(String[] args) 
        new BackTrace().f2();
    

在这个方法中,我们并没有重写Person中的equals和hashCode,调用这个方法,发现并没有输入Hello World这句话。
我们现在重写equals和hashCode

    @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) &&
                Objects.equals(address, person.address);
    

    @Override
    public int hashCode() 
        return Objects.hash(name, age, address);
    

结果,我们就发现输出了。

总结:
1、equals 是 Object 中的方法,实现方式还是==
2、各个包装类都重写了equals方法,来实现自己的比较逻辑,String类也重写了equals方法。
3、自定义的类比较也要重写equals方法
4、重写equals方法必须重写hashCode;

三、为什么重写equals方法必须重写hashCode;
1、hashCode也是Object中的方法,看源码如何实现的。
2、native关键字说明这个方法是原生函数,也就是这个方法是用c/c++语言实现的,并且被编译成了DLL,
由Java去调用,这些函数的实现体在DLL中,JDK源代码中并不包含,你应该是看不到的。

一般情况下,对象不同,hashCode也就不同
下面的是特殊情况:

System.out.println("Aa".hashCode());
System.out.println("BB".hashCode());
2112
2112

已经重写了equals,当姓名和年龄相同的时候,就认为他们是一个人。
这里的p1和p2就是同一个人,那么我们用Map去存储他的分数(没有重写hashCode),正常情况下,我们期望返回95。
我们现在将Person中的hashCode去掉,然后用HashMap去存储和获取。

    public static void main(String[] args) 
        //Person为键,String为值
        HashMap<Person,String> map = new HashMap<>();
        map.put(new Person("张三",10,"北京"),"95");
        String s = map.get(new Person("张三", 10, "北京"));
        System.out.println(s);
    


会发现返回的是Null
所以并没有通过张三这个对象拿到对应的分数,如果将这个hashCode解开会发现拿到了这个分数。
HashMap的存储方式

    public V put(K key, V value) 
        return putVal(hash(key), key, value, false, true);
    
   static final int hash(Object key) 
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    

HashMap的存储方式,使用key的hash值 作为Key来存储对应的值。
如果我们没有重写hashCode的时候,我们会用Object自带的hashCode去存储,如果重写了hashCode,我们会以年龄和姓名进行获取返回相同的hash值。
3、HashMap哈HashSet的存取都是这样实现的。
4、发生了hash碰撞怎么办,调用equals方法
5、HashMap的存储结构时怎么样的?
对于equals和hashCode,Object规范:
1、在应用程序执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对同一个对象的多次调用,hashCode方法都必须始终返回同一个值。
(对于Person中的姓名和年龄等属性相同,那么hashCode返回同一个值)
2、如果两个对象根据equals方法比较是相等的,那么调用这两个对象的hashCode方法都必须产生同样的整数结果
3、如果两个对象根据equals方法比较是不相等的,那么调用这两个对象中的hashCode的方法则不一定要求hashCode方法必须产生不同的结果。
但是开发人员应该知道,给不相等的对象产生不同的整数结果,有可能提高散列表的性能。
总结:
1、HashCode也是Object中的方法
2、Native关键字说明这个方法是原生函数,也就是这个方法是用c/c++语言实现的,我们看不到源码的实现。
3、一般情况下,对象不同(equals不同),hashCode一般不同,但是对象相同(equals相同),hashCode一定相同。
4、HashMap和HashSet的存取都是通过key的hash值来存储的

以上是关于Java 为什么重写equals的时候必须重写hashCode的主要内容,如果未能解决你的问题,请参考以下文章

为啥重写equals方法,一定要重写HashCode方法?

java开发----自定义对象,重写equals方法

重写equals就必须重写hashCode的原理分析

为什么要重写hashcode和equals方法?初级程序员在面试中很少能说清楚。

重写equals 方法的注意事项

为什么重写equals()就必须重写hashCode(),什么情况下可以不重写hashCode()