【Java学习】equals()和hashcode()方法详解

默认情况下,java.lang.object提供了两个重要方法来进行对象之间的比较:equals()hashcode()。这两个方法在大型项目中多个类之间的交互中非常有用。在这篇文章中,主要介绍这里两个方法之间的联系、默认实现、以及开发者重写这两个方法的场景。

方法定义以及默认实现

  • equals(Object obj) java.lang.Object中定义的用来表明一个其他对象是否等同于(equal to)当前对象。jdk中Object中的默认实现是基于两个对象的内存位置——即当且仅当两个对象所占内存地址相同,这两个对象才等同。
  • hashcode() 这个方法会返回一个随机整型数值,在处理哈希表(hash table)时大有裨益(详见后续的HashMap讲解)。

equals()和hashcode()之间联系

Object中两个方法的默认实现常常不能满足业务需求,经常需要根据业务需求来确定两个对象是否相等。
根据Java文档,这两个方法均需要被重写才能保证万无一失——也就是说,单纯重写equals()方法是不够

If two objects are equal according to the equals(Object) method, then calling the hashcode() method on each of the two objects must produce the same integer result.
译文:如果两个对象等同(equals()方法返回值为true),那么这两个对象调用hashcode()方法时,得到的整型数值必须相等。

下面的实例用来展示同时重写两个方法的必要性以及只重写equals()方法的弊端。

实例

定义一个学生类:

package org.my.beans;
public class Student {
    private int id;
    private String name;
    public Student(int id, String name) {
        this.name = name;
        this.id = id;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

同时定义一个HashcodeEquals 类,用于验证两个Student 实例(具有相同属性)是否等同。代码如下:

public class HashcodeEquals {
    public static void main(String[] args) {
        Student alex1 = new Student(1, "Alex");
        Student alex2 = new Student(1, "Alex");
        System.out.println("alex1 hashcode = " + alex1.hashCode());
        System.out.println("alex2 hashcode = " + alex2.hashCode());
        System.out.println("Checking equality between alex1 and alex2 = " + alex1.equals(alex2));
    }
}

输出:

alex1 hashcode = 3459493747
alex2 hashcode = 7423689063
Checking equality between alex1 and alex2 = false

结论:
即使两个对象具有相同的属性值,当时它们存储在不同的内存地址中。因此它们在默认equals() 实现下被认为是不等同的。hashcode() 也同理——生成的两个整型数值并不相等。

重写equals()方法

假如在业务场景中,我们认为两个学生对象如果具有相同id,那么我们认为这两个学生对象是等同的。所以我们重写equals() 方法以实现上述业务需求,重写代码如下:

@Override
public boolean equals(Object obj) {
    if (obj == null) return false;
    if (!(obj instanceof Student))
        return false;
    if (obj == this)
        return true;
    return this.getId() == ((Student) obj).getId();
}

通过以上代码可以看出,当且仅当具有相同内存地址或者相同id 两个Student对象才认定为等同。重写equals() 后,继续执行一遍上面介绍的HashcodeEquals,得到输出如下:

alex1 hashcode = 2032578917
alex2 hashcode = 1531485190
Checking equality between alex1 and alex2 = true

重写hashcode()

通过在上面重写equals()方法,我们完成了业务需求——即使两个相同id 的学生对象具有不同的hashcode值。那么重写hashcode() 的目的是什么呢?

我们通过一个HashSet中存放两个相同id 的学生来进行验证。

public class HashcodeEquals {
    public static void main(String[] args) {
        Student alex1 = new Student(1, "Alex");
        Student alex2 = new Student(1, "Alex");
        HashSet < Student > students = new HashSet < Student > ();
        students.add(alex1);
        students.add(alex2);
        System.out.println("HashSet size = " + students.size());
        System.out.println("HashSet contains Alex = " + students.contains(new Student(1, "Alex")));
    }
}

我们得到结果如下:

HashSet size = 2
HashSet contains Alex = false

AHA!我们已经重写equals()并且已经验证alex1、alex2是等同的,我们都知道HashSet只存储不同对象,那么为什么它认为他们是不同的对象?

HashSet将在内存中将对象在不同桶(bucket)进行存储。每个桶都是链接到一个特定的hashcode。由于alex1以及alex2有不同的hashcode,它们将存储在不同的桶中,因此,将alex2认为是一个与alex1完全不同的对象。

现在再重写hashcode() ,使得学生对象的hashcode等于其id ,使得id相同的学生存储在同一个桶:

@Override
public int hashCode() {
    return id;
}

得到输出结果如下:

HashSet size = 1
HashSet contains Alex = true

看吧! 现在两个学生对象才是真正意义上的等同,这就是重写hashcode() 的重要性。

HashMap,HashTable等利用hash机制的结构也均有类似的性质。

结论

在工作用业务需要需要重写equals()以及hashcode()时,需要遵从以下原则:
1、如果两个对象equals()方法返回值为true,那么一定一定一定要具有相同的hashcode
2、如果两个对象具有相同的hashcode,那么这两个对象未必等同。(equals返回未必为true)。
3、仅重写equals()方法会使得HashSet,HashMap,HashTable等用到hash机制的结构出现问题。

希望对大家有所帮助。

猜你喜欢

转载自blog.csdn.net/u013190088/article/details/81106075