1. 引言
NullPointerException
是 Java 中最常见的异常之一。当访问或操作指向 null
的引用变量时就会触发。验证对象(尤其是作为方法或构造函数参数时)对确保代码健壮性至关重要。 我们可以通过手动编写空检查代码、使用第三方库,或选择更便捷的方案来实现。
本教程将重点介绍后者——Java 内置的 Objects.requireNonNull()
方法,这是一个灵活且强大的解决方案。
2. 空值处理
简单回顾下,避免手动 null
检查 有多种替代方案。与其使用容易出错且耗时的 if
语句包裹代码,不如选择各种库:Spring、Lombok (@NonNull
) 和 Uber 的 NullAway 都是不错的选择。
相反,如果我们希望保持代码一致性、避免引入额外依赖,或使用原生 Java,可以在 Optional
类和 Objects
方法间选择。虽然 Optional
简化了空值处理,但在简单场景中会增加编码开销并使代码臃肿。作为独立对象,它还会消耗额外内存。此外,Optional
只是将 NullPointerException
转换为 NoSuchElementException
,并未真正解决问题。
而 Objects
类提供了高效操作对象的静态工具方法。 自 Java 7 引入并在 Java 8/9 持续更新,它还提供了在执行操作前验证条件的方法。
3. Objects.requireNonNull() 的优势
Objects
类通过一组 requireNonNull()
静态方法,简化了空值检查和处理。 这些方法为使用前检查 null
对象提供了直接且简洁的途径。
requireNonNull()
方法的核心作用是:检查对象引用是否为 null
,若是则抛出 NullPointerException
。 通过显式抛出异常,我们明确传达了检查意图,使代码更易理解和维护。这也告知开发者该错误是故意的,防止后续无意中修改行为。
Objects
类属于 java.util
包,无需外部库即可轻松使用。 其方法文档完善,为开发者提供了清晰的使用指导。
4. 用例和重载方法
现在我们聚焦 requireNonNull()
的不同用例和重载变体。这些方法支持从简单 null
检查到带自定义消息的复杂验证等多种场景。
此外还有两个支持默认值的方法:requireNonNullElse()
和 requireNonNullElseGet()
。理解这些用例有助于有效集成 requireNonNull()
到代码库中。
4.1. 方法和构造函数中的单参数验证
先看最简单的实现——单参数 requireNonNull()
:
public static <T> T requireNonNull(T obj) {
if (obj == null) {
throw new NullPointerException();
} else {
return obj;
}
}
该方法检查提供的对象引用是否为 null
。若是则抛出 NullPointerException
,否则返回原对象。
主要用于方法和构造函数的参数验证。 例如,确保 greet()
方法的 name
参数不为 null
:
void greet(String name) {
Objects.requireNonNull(name, "Name cannot be null");
logger.info("Hello, {}!", name);
}
@Test
void givenObject_whenGreet_thenNoException() {
Assertions.assertDoesNotThrow(() -> greet("Baeldung"));
}
@Test
void givenNull_whenGreet_thenException() {
Assertions.assertThrows(NullPointerException.class, () -> greet(null));
}
使用 requireNonNull()
可确保方法接收有效参数,在访问对象前检测 null
,避免后续操作出错。
4.2. 带自定义错误消息的多参数验证
接下来看方法或构造函数接收多参数的场景。Objects.requireNonNull()
的一个变体接受对象引用和 String
消息两个参数,当对象为 null
时抛出带自定义消息的异常:
public static <T> T requireNonNull(T obj, String message) {
if (obj == null)
throw new NullPointerException(message);
return obj;
}
可用于多参数构造函数的验证:
static class User {
private final String username;
private final String password;
public User(String username, String password) {
this.username = Objects.requireNonNull(username, "Username is null!");
this.password = Objects.requireNonNull(password, "Password is null!");
}
// getters
}
@Test
void givenValidInput_whenNewUser_thenNoException() {
Assertions.assertDoesNotThrow(() -> new User("Baeldung", "Secret"));
}
@Test
void givenNull_whenNewUser_thenException() {
Assertions.assertThrows(NullPointerException.class, () -> new User(null, "Secret"));
Assertions.assertThrows(NullPointerException.class, () -> new User("Baeldung", null));
}
带自定义消息的 NullPointerException
能提供更多上下文信息,明确指出错误原因和问题参数,便于调试。
4.3. 使用 Supplier 延迟消息创建
最后通过另一个重载方法自定义异常消息:
public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier) {
if (obj == null) {
throw new NullPointerException(messageSupplier == null ? null : (String)messageSupplier.get());
} else {
return obj;
}
}
第二个参数是错误消息的 Supplier
,允许延迟消息创建直到 null
检查之后,可提升性能。这对字符串拼接或复杂计算等高成本操作尤其有价值:
void processOrder(UUID orderId) {
Objects.requireNonNull(orderId, () -> {
String message = "Order ID cannot be null! Current timestamp: " + getProcessTimestamp();
message = message.concat("Total number of invalid orders: " + getOrderAmount());
message = message.concat("Please provide a valid order.");
return message;
});
logger.info("Processing order with id: {}", orderId);
}
private static int getOrderAmount() {
return new Random().nextInt(100_000);
}
private static Instant getProcessTimestamp() {
return Instant.now();
}
@Test
void givenObject_whenProcessOrder_thenNoException() {
Assertions.assertDoesNotThrow(() -> processOrder(UUID.randomUUID()));
}
@Test
void givenNull_whenProcessOrder_thenException() {
Assertions.assertThrows(NullPointerException.class, () -> processOrder(null));
}
上例中 getOrderAmount()
和 getProcessTimestamp()
可能涉及耗时操作(如数据库查询或外部 API 调用)。延迟消息创建可避免不必要的性能开销——当 orderId
不为 null
时不会执行这些操作。
但需注意:确保创建 Supplier
的开销低于直接生成 String
消息,否则得不偿失。
5. 最佳实践
如前所述,设计方法和构造函数时,需强制参数约束以确保代码行为可预测。 在方法或构造函数开头使用 Objects.requireNonNull()
可及早捕获无效参数,保持代码简洁、易维护且易调试。
requireNonNull()
对构建快速失败系统至关重要。 快速失败原则要求错误立即暴露,避免级联故障并降低调试复杂度。若方法预先验证参数,会以明确异常快速失败,使问题根源一目了然。这些检查能防止系统产生令人困惑的错误、错误结果,甚至影响无关代码模块。
另一良好实践是为 public 或 protected 方法显式记录参数约束。 可用 Javadoc 的 @throws
标注指定违反约束时抛出的异常。若多个方法抛出 NullPointerException
,可在类级别 Javadoc 中统一说明,避免每个方法重复记录。
6. 结论
本教程展示了如何使用 Objects.requireNonNull()
及其重载变体高效验证方法和构造函数参数。 这些方法为 Java 中的空检查提供了简单而强大的解决方案。内置的自定义错误消息和延迟消息创建功能增加了灵活性,使其适用于多种场景。
此外,采用参数约束强制、快速失败原则和异常文档化等最佳实践,能显著提升代码库的整体质量和可维护性。