1. 认识 Objects 类
进入正题!如果你厌倦了手动对值进行 null 检查,并为每个字段编写哈希计算,那么实用类 java.util.Objects 可以帮上忙。它的目标是让对象相关的工作更简单、更简洁、更安全。
它的确是一把“瑞士军刀”:该类能安全地判断对象是否相等(避免抛出 NullPointerException)、便捷地计算哈希码、通过比较器进行比较,以及检查参数是否为 null。
Objects.equals:考虑 null 的安全比较
如果直接写 a.equals(b),而 a 为 null,就会抛出 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.hash 与 hashCode:简洁地计算哈希
在同时重写 hashCode 和 equals 时,很容易出错,尤其当字段很多时。手写代码往往既臃肿又脆弱:
@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 正确实现 equals、hashCode 和 compareTo
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.equals 和 Objects.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 类的主要方法
| 方法 | 用途 | 使用示例 |
|---|---|---|
|
考虑 null 的两个对象的安全比较 | |
|
按多个字段简洁地计算哈希码 | |
|
通过比较器进行比较,对 null 安全 | |
|
进行 null 检查,抛出 NullPointerException | |
|
检查 null/非 null(在 Stream API 中很方便) | |
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。
GO TO FULL VERSION