CodeGym /课程 /JAVA 25 SELF /Objects 类:equals、hashCode、hash 方法

Objects 类:equals、hashCode、hash 方法

JAVA 25 SELF
第 29 级 , 课程 1
可用

1. 认识 Objects

进入正题!如果你厌倦了手动对值进行 null 检查,并为每个字段编写哈希计算,那么实用类 java.util.Objects 可以帮上忙。它的目标是让对象相关的工作更简单、更简洁、更安全。

它的确是一把“瑞士军刀”:该类能安全地判断对象是否相等(避免抛出 NullPointerException)、便捷地计算哈希码、通过比较器进行比较,以及检查参数是否为 null

Objects.equals:考虑 null 的安全比较

如果直接写 a.equals(b),而 anull,就会抛出 NullPointerException。手动检查很繁琐。Objects.equals(a, b) 会替你处理:

  • 如果二者都为 null — 返回 true
  • 如果只有一个为 null — 返回 false
  • 如果都不为 null — 调用常规的 equals
import java.util.Objects;

String a = null;
String b = "Java";
System.out.println(Objects.equals(a, b)); // false

String c = null;
System.out.println(Objects.equals(a, c)); // true

String d = "Java";
String e = "Java";
System.out.println(Objects.equals(d, e)); // true

为什么方便? 代码更短、更整洁,并且避免偶发的 NPE。

2. Objects.hashhashCode:简洁地计算哈希

在同时重写 hashCodeequals 时,很容易出错,尤其当字段很多时。手写代码往往既臃肿又脆弱:

@Override
public int hashCode() {
    int result = 17;
    result = 31 * result + (name != null ? name.hashCode() : 0);
    result = 31 * result + age;
    return result;
}

方法 Objects.hash 解决了这个问题——简洁、安全,并且天然支持 null

import java.util.Objects;

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

    // ... 构造器、getter 等

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

重要说明:Objects.hash 使用可变参数并会创建数组——在极少数高负载的热点路径上,手写的 hashCode 可能更快。对大多数应用来说差异可以忽略。

3. Objects.compare:把比较委托给比较器

有时需要通过预先准备好的 Comparator 来比较两个对象。除了直接调用 comparator.compare(a, b),也可以使用:

int result = Objects.compare(a, b, comparator);

该方法:

  • 当对象相等时返回 0
  • 认为 null 比任何非 null 对象“小”。
  • 其他情况下,将逻辑委托给传入的比较器。
import java.util.Comparator;
import java.util.Objects;

class Person {
    private String name;
    Person(String name) { 
        this.name = name; 
    }
    public String getName() { 
        return name; 
    }
}

public class Main {
    public static void main(String[] args) {
        Person a = new Person("Anna");
        Person b = new Person("Boris");
        Comparator<Person> byName = Comparator.comparing(Person::getName);

        System.out.println(Objects.compare(a, b, byName)); // <0,因为 "Anna" < "Boris"
        System.out.println(Objects.compare(a, null, byName)); // >0,因为 a != null
        System.out.println(Objects.compare(null, b, byName)); // <0,因为 null < b
        System.out.println(Objects.compare(null, null, byName)); // 0
    }
}

4. Objects.requireNonNull:防止“隐形”错误

如果方法只应接受非-null 值,请立即进行检查。Objects.requireNonNull 会抛出 NullPointerException 并带上你的消息:

public void setName(String name) {
    this.name = Objects.requireNonNull(name, "name 不能为空");
}

5. 示例:使用 Objects 正确实现 equalshashCodecompareTo

import java.util.Objects;

public class Person implements Comparable<Person> {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = Objects.requireNonNull(name, "name 不能为空");
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true; // 按引用比较
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        // 对 null 友好的比较
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age); // 简洁且安全
    }

    @Override
    public int compareTo(Person other) {
        // 先按姓名比较,再按年龄比较
        int cmp = name.compareTo(other.name);
        if (cmp != 0) return cmp;
        return Integer.compare(age, other.age);
    }
}

现在你可以将对象放入 HashSet,把它们作为 HashMap 的键,进行相等性比较并对列表进行排序(例如通过 Collections.sort)。

6. 在真实任务中的应用:减少代码并降低错误率

示例:应用中的用户列表

由于正确实现了 equals/hashCode,在集合中的查找将表现可预期:

import java.util.ArrayList;
import java.util.List;

List<Person> users = new ArrayList<>();
users.add(new Person("Anna", 25));
users.add(new Person("Boris", 30));

Person search = new Person("Anna", 25);
System.out.println(users.contains(search)); // true

示例:包含可为 nullable 的字段

如果类中有可能为 null 的字段(例如 middleName),请使用 Objects.equalsObjects.hash

import java.util.Objects;

public class User {
    private String firstName;
    private String middleName; // 可以为 null
    private String lastName;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(firstName, user.firstName)
            && Objects.equals(middleName, user.middleName)
            && Objects.equals(lastName, user.lastName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstName, middleName, lastName);
    }
}

7. 表格:Objects 类的主要方法

方法 用途 使用示例
Objects.equals(a, b)
考虑 null 的两个对象的安全比较
Objects.equals(a, b)
Objects.hash(a, b, ...)
按多个字段简洁地计算哈希码
Objects.hash(name, age)
Objects.compare(a, b, comparator)
通过比较器进行比较,对 null 安全
Objects.compare(p1, p2, byNameComparator)
Objects.requireNonNull(obj[, msg])
进行 null 检查,抛出 NullPointerException
Objects.requireNonNull(name, "name 不能为空")
Objects.isNull(obj) / Objects.nonNull(obj)
检查 null/非 null(在 Stream API 中很方便)
list.stream().filter(Objects::nonNull)

8. 使用 Objects 类方法的常见错误

错误 1:忘记使用 Objects.equals 处理可为 null 的字段。 如果直接通过 equals 比较字段,可能会触发 NullPointerException。请使用 Objects.equals(middleName, other.middleName)

错误 2:在 hashCode 中遗漏了参与比较的字段。 参与 equals 的字段也必须参与 hashCode,否则 HashSet/HashMap 的行为将变得不可预测。

错误 3:手写 hashCode 出错。 手动选择系数并处理 null 并不容易。Objects.hash 已经替你处理好了;如果没有严格的性能要求,就用它吧。

错误 4:在类契约要求处没有使用 Objects.requireNonNull 如果字段不允许为 null,请在构造器/Setter 中检查——错误会立刻暴露,而不会“埋”在调用栈的深处。

错误 5:对数组使用 Objects.hash 对于数组应使用 Arrays.hashCode,多维数组应使用 Arrays.deepHashCode;同理,比较内容应使用 Arrays.equals/Arrays.deepEquals

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION