ZetCode

Java 空指针异常

最后修改于 2025 年 4 月 2 日

NullPointerException (NPE) 是一种运行时异常,当您尝试将空引用当作实际对象引用使用时,就会发生这种异常。 它是 Java 应用程序中最常见的异常之一。

在本教程中,我们将探讨导致 NullPointerExceptions 的原因,如何预防它们,以及 Java 中空值处理的最佳实践。我们将介绍处理空引用的基本和高级技巧。

导致空指针异常的原因

当您的代码尝试执行以下操作时,会发生 NullPointerException

NPEExamples.java
void main() {

    String str = null;
    
    // Method invocation on null
    System.out.println(str.length());  // NPE
    
    // Field access on null
    Person p = null;
    System.out.println(p.name);  // NPE
    
    // Array access on null
    int[] numbers = null;
    System.out.println(numbers[0]);  // NPE
}

所有这些操作都会抛出 NullPointerException,因为它们尝试将空引用当作适当的对象引用使用。

基本的空值检查

防止 NullPointerException 最简单的方法是在使用对象引用之前显式检查是否为空。

NullCheck.java
void printLength(String str) {

    if (str != null) {
        System.out.println(str.length());
    } else {
        System.out.println("String is null");
    }
}

此方法通过首先检查引用来安全地处理空输入。虽然有效,但如果过度使用,这种方法可能会导致代码混乱。

使用 Objects.requireNonNull

Java 7 引入了 Objects.requireNonNull,用于更优雅的空值检查,尤其是在参数验证方面。

RequireNonNull.java
import java.util.Objects;

void processUser(User user) {
    Objects.requireNonNull(user, "User cannot be null");
    // Safe to use user object here
}

如果对象为空,requireNonNull 将抛出带有指定消息的 NullPointerException。 这比手动空值检查更简洁,更适合验证。

Optional 用于空值处理

Java 8 引入了 Optional,以提供一种更优雅的方式来处理潜在的空值,而无需显式检查。

OptionalExample.java
import java.util.Optional;

void printUsername(User user) {

    Optional.ofNullable(user)
            .map(User::getName)
            .ifPresentOrElse(
                name -> System.out.println("Username: " + name),
                () -> System.out.println("No user provided")
            );
}

Optional 提供了一个丰富的 API,用于处理潜在的空值,而无需直接进行空值检查。 这种方法使代码更具可读性和声明性。

空对象模式

空对象模式是一种设计模式,它提供了一个接口的特殊实现,用于表示实际对象的缺失。 替代使用 null 引用,返回或使用“空对象”,该对象实现预期的接口,但提供中性(不执行任何操作)的行为。 这种方法消除了在整个代码中进行显式 null 检查的需要,从而简化了逻辑并降低了 NullPointerException 的风险。

NullObjectPattern.java
interface Logger { 

    void log(String message); 
}

class ConsoleLogger implements Logger { 

    public void log(String message) {
        System.out.println(message); 
    } 
}

class NullLogger implements Logger { 

    public void log(String message) { 
        // Donothing 
    }  
}

void process(Logger logger) { 

    // No null check needed - NullLogger handles the null case 
    logger.log("Processing started"); 
}

在此示例中,Logger 接口定义了记录消息的约定。 ConsoleLogger 类是一个具体的实现,它将消息记录到控制台,而 NullLogger 类提供了一个“空对象”实现,当调用 log 方法时,它什么也不做。

通过使用 NullLoggerprocess 方法避免了在调用 logger.log() 之前进行空值检查的需要。 这保持了代码的清晰,并防止了潜在的 NullPointerException 错误。

空对象模式的主要优点是它将“不执行任何操作”的行为封装在与真实对象相同的接口的类中。 这带来了一些好处

代码简化:通过用 NullLogger 替换 null 引用,代码变得更简单,因为不再需要空值检查。

错误预防:该模式防止了意外取消对 null 的引用,从而降低了 NullPointerException 的可能性。

多态性保留:空对象的使用保持了代码的多态行为。 客户端代码可以依赖于接口并将空对象视为任何其他具体实现,从而可以在没有特殊情况的情况下使用相同的逻辑。

提高可读性:重复的空值检查的缺失提高了代码的可读性,使开发人员的意图更加清晰。

但是,空对象模式并不适用于所有情况。 当对象的缺失具有有意义的、一致的行为(例如,“不执行任何操作”)并且当预期的接口适合中性实现时,它效果最佳。 例如,Logger 接口自然地适应“不执行任何操作”的行为,但需要复杂交互的接口可能不会从此方法中受益。

通过应用空对象模式,开发人员可以使他们的代码更健壮、简洁和可读,同时降低由 null 引用引起的运行时错误的风险。

用于空值安全的注解

各种注解库(@NonNull,@Nullable)可以帮助记录和强制执行代码中的空值约定。

NullAnnotations.java
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;

public class UserService {

    public User findUser(@NonNull String username) {
        // Parameter is guaranteed non-null
    }
    
    public @Nullable User findUserById(Long id) {
        // May return null
    }
}

这些注解可以帮助 IDE 和静态分析工具在运行时之前检测潜在的空指针问题。

最佳实践

为了最大限度地减少代码中的 NullPointerExceptions

来源

Java 空指针异常文档

本教程涵盖了 Java 中 NullPointerException 的基本方面,包括原因、预防技术以及在应用程序中实现健壮的空值处理的最佳实践。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 我自 2007 年以来一直在撰写编程文章。 迄今为止,我已经撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有Java教程