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 中的空检查提供了简单而强大的解决方案。内置的自定义错误消息和延迟消息创建功能增加了灵活性,使其适用于多种场景。

此外,采用参数约束强制、快速失败原则和异常文档化等最佳实践,能显著提升代码库的整体质量和可维护性。