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

【Java 为什么重写equals的时候必须重写hashCode】,第1张

【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方法(为什么?)

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 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值来存储的

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5696644.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存