理解 `hashCode()` 与 `equals()` 方法在集合中的重要性

hashCode()equals():集合背后的爱情故事

各位看官,今天咱们不聊风花雪月,也不谈柴米油盐,咱们聊聊Java世界里一对相爱相杀,又不可或缺的好基友:hashCode()equals()。 这俩哥们,在集合的世界里,那可是扛把子的存在,没有他们,集合就只能是一盘散沙,毫无秩序可言。

故事的开端:集合的烦恼

话说在很久很久以前(其实也就Java诞生那会儿),人们需要一种容器来存放各种各样的数据。 于是,数组应运而生。 数组这玩意儿,简单粗暴,直接用索引访问,效率也高。 但是,数组有个致命的缺点:大小固定。 这就像住在一个固定大小的房子里,东西多了就没地儿放,想搬家又麻烦。

为了解决这个问题,集合框架闪亮登场! ArrayList、LinkedList、HashSet、HashMap…各种各样的集合就像雨后春笋般冒了出来。 这些集合,大小可变,功能强大,简直是程序员的福音。

但是,问题也来了。 如何判断集合里是否已经存在某个元素? 如何快速找到某个元素? 这就涉及到元素之间的比较问题。

equals():你是我的唯一

equals() 方法,就是用来判断两个对象是否“相等”的。 注意,这里的“相等”和我们平时说的相等不太一样。 在Java的世界里,对象是引用类型,== 比较的是两个对象的引用是否指向同一个内存地址。 如果两个对象的内容一样,但是引用不同,== 会返回 false

equals() 方法,允许我们自定义对象相等的标准。 默认情况下,equals() 方法和 == 的行为是一样的,比较的是引用。 但是,我们可以重写 equals() 方法,根据对象的属性来判断是否相等。

举个栗子:

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

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

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

    // 省略 hashCode() 方法
}

public class EqualsExample {
    public static void main(String[] args) {
        Person p1 = new Person("张三", 20);
        Person p2 = new Person("张三", 20);
        Person p3 = new Person("李四", 22);

        System.out.println("p1 == p2: " + (p1 == p2)); // false
        System.out.println("p1.equals(p2): " + p1.equals(p2)); // true
        System.out.println("p1.equals(p3): " + p1.equals(p3)); // false
    }
}

在这个例子中,我们重写了 Person 类的 equals() 方法。 我们认为,如果两个 Person 对象的姓名和年龄都相同,那么这两个对象就是相等的。 因此,p1.equals(p2) 返回 true,而 p1 == p2 返回 false

hashCode():快速定位的小助手

有了 equals() 方法,我们可以判断两个对象是否相等了。 但是,如果要在集合中查找某个元素,难道要遍历整个集合,逐个比较 equals() 吗? 如果集合很大,那效率也太低了!

这时候,hashCode() 方法就派上用场了。 hashCode() 方法返回一个对象的哈希码,哈希码是一个整数,可以用来快速定位对象在集合中的位置。

想象一下,你有一个巨大的图书馆,里面堆满了书。 你要找一本特定的书,如果一本一本的找,那得找到猴年马月。 但是,如果你给每本书都贴上一个标签(哈希码),然后把书按照标签分类存放,那么你就可以很快找到你要的书了。

hashCode() 方法的作用就是给对象贴标签。 不同的对象,哈希码可能相同,也可能不同。 但是,如果两个对象相等(equals() 返回 true),那么它们的哈希码必须相同。 这是 hashCode() 方法和 equals() 方法之间最重要的约定。

hashCode()equals() 的爱情故事:相辅相成,缺一不可

hashCode()equals() 方法就像一对情侣,相辅相成,缺一不可。 如果只重写了 equals() 方法,而没有重写 hashCode() 方法,那么在使用某些集合(比如 HashSet、HashMap)时,可能会出现意想不到的问题。

举个栗子:

import java.util.HashSet;
import java.util.Set;

public class HashCodeExample {
    public static void main(String[] args) {
        Person p1 = new Person("张三", 20);
        Person p2 = new Person("张三", 20);

        Set<Person> personSet = new HashSet<>();
        personSet.add(p1);
        personSet.add(p2);

        System.out.println("personSet size: " + personSet.size()); // 2
        System.out.println("personSet contains p1: " + personSet.contains(p1)); // true
        System.out.println("personSet contains p2: " + personSet.contains(p2)); // false
    }
}

在这个例子中,我们创建了两个 Person 对象 p1p2,它们的姓名和年龄都相同,所以 p1.equals(p2) 返回 true。 但是,由于我们没有重写 hashCode() 方法,所以 p1p2 的哈希码很可能不同。

当我们把 p1p2 添加到 HashSet 中时,HashSet 会根据哈希码把它们放到不同的位置。 结果,HashSet 中就出现了两个“相等”的对象,这违反了 HashSet 的特性:不允许重复元素。

更糟糕的是,当我们使用 personSet.contains(p2) 来判断 HashSet 中是否包含 p2 时,HashSet 会先计算 p2 的哈希码,然后根据哈希码找到对应的位置,再比较该位置上的元素和 p2 是否相等。 由于 p2 的哈希码和 p1 的哈希码不同,所以 HashSet 认为 p2 不存在。

为了解决这个问题,我们需要同时重写 hashCode() 方法和 equals() 方法。

import java.util.Objects;

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

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

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

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

import java.util.HashSet;
import java.util.Set;

public class HashCodeExample {
    public static void main(String[] args) {
        Person p1 = new Person("张三", 20);
        Person p2 = new Person("张三", 20);

        Set<Person> personSet = new HashSet<>();
        personSet.add(p1);
        personSet.add(p2);

        System.out.println("personSet size: " + personSet.size()); // 1
        System.out.println("personSet contains p1: " + personSet.contains(p1)); // true
        System.out.println("personSet contains p2: " + personSet.contains(p2)); // true
    }
}

在这个例子中,我们重写了 Person 类的 hashCode() 方法,使用了 Objects.hash() 方法来生成哈希码。 Objects.hash() 方法会根据对象的属性生成哈希码,保证了如果两个对象的属性相同,那么它们的哈希码也相同。

现在,当我们把 p1p2 添加到 HashSet 中时,HashSet 会根据哈希码把它们放到同一个位置。 由于 p1.equals(p2) 返回 true,所以 HashSet 会认为 p2p1 是同一个对象,不会重复添加。

hashCode()equals() 的约定

为了保证集合的正确性,hashCode() 方法和 equals() 方法必须遵守以下约定:

  1. 一致性: 如果两个对象相等(equals() 返回 true),那么它们的哈希码必须相同。
  2. 对称性: 如果 a.equals(b) 返回 true,那么 b.equals(a) 必须返回 true
  3. 传递性: 如果 a.equals(b) 返回 true,并且 b.equals(c) 返回 true,那么 a.equals(c) 必须返回 true
  4. 非空性: a.equals(null) 必须返回 false
  5. 一致性(hashCode()): 如果对象的属性没有改变,那么多次调用 hashCode() 方法必须返回相同的哈希码。

哪些集合需要重写 hashCode()equals()

主要是一些基于哈希表的集合,比如:

  • HashSet
  • HashMap
  • LinkedHashSet
  • LinkedHashMap

这些集合使用哈希码来快速定位元素,因此,如果你的自定义对象要存储在这些集合中,就必须重写 hashCode()equals() 方法。

如何正确重写 hashCode()equals()

重写 hashCode()equals() 方法看似简单,但实际上有很多坑。 下面是一些建议:

  1. 使用 IDE 自动生成: 大多数 IDE(比如 IntelliJ IDEA、Eclipse)都提供了自动生成 hashCode()equals() 方法的功能。 这可以避免很多手误。
  2. 使用 Objects.equals()Objects.hash() Objects.equals() 方法可以避免空指针异常,Objects.hash() 方法可以方便地生成哈希码。
  3. 考虑所有重要的属性: 在重写 equals() 方法时,要考虑所有影响对象相等性的属性。 在重写 hashCode() 方法时,也要使用所有这些属性来生成哈希码。
  4. 保持一致性: 确保 hashCode() 方法和 equals() 方法保持一致。 如果两个对象相等,那么它们的哈希码必须相同。

总结:

hashCode()equals() 方法是Java集合框架中非常重要的两个方法。 它们就像一对情侣,相辅相成,缺一不可。 只有正确地重写了这两个方法,才能保证集合的正确性和效率。

下面用一个表格总结一下:

方法 作用 场景 是否需要重写
equals() 用于判断两个对象是否相等。 默认比较的是对象的引用,可以通过重写来比较对象的属性。 当需要自定义对象相等的标准时,比如在集合中判断元素是否重复。
hashCode() 用于生成对象的哈希码,哈希码是一个整数,可以用来快速定位对象在集合中的位置。 如果两个对象相等(equals() 返回 true),那么它们的哈希码必须相同。 当对象需要存储在基于哈希表的集合中时,比如 HashSetHashMap
== 用于比较两个对象的引用是否指向同一个内存地址。 通常用于比较基本类型的值,或者判断两个对象是否是同一个对象。

最后的忠告:

重写 hashCode()equals() 方法是一项重要的任务,需要认真对待。 不要为了偷懒而随便重写,否则可能会导致意想不到的问题。 记住,代码质量的提高,往往就在这些细节之中。

希望这篇文章能够帮助你更好地理解 hashCode()equals() 方法,并在实际开发中正确使用它们。 祝你编程愉快!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注