如何使用 Java Optional 来处理 NullPointerException?


我们都知道处理空引用的痛苦和NullPointerExceptions的可能性。这就是Optional 的用武之地,它是一个容器对象,可能包含也可能不包含非空值,并提供各种实用方法来检查值是否存在。

在本文中,我们将讨论检查空引用的方法以及Optional 如何帮助我们避免 NPE。无论您是经验丰富的 Java 开发人员还是新手,本文都将提供有关处理代码中空引用的最佳实践的宝贵见解。


空引用问题
假设您正在构建一个图书馆管理系统。您正在使用 java 获取带有书名的书籍详细信息。

@Repository
public class BookRepository extends JpaRepository<Book, Long> {
    Book findByTitle(String title);
}

public class SearchService {
    BookRepository bookRepository;
    
    Book searchByTitle(String title) {
        return bookRepository.findByTitle(title);
    }
}

这里BookRepository连接到我们的数据库,SearchService处理API请求。BookRepository中的findByTitle只有在我们的数据库中存在一个标题时才会返回一个Book对象。

那么,如果没有相同书名的书,会发生什么?

该函数将返回一个空引用。

因为,这个函数可以用于其他功能,如借阅、购买、阅读和其他。如果空引用处理不当,这些功能就会中断,并可能导致NullPointerException。

在Optional之前的空值检查
在Optional之前,我们还有其他替代空参考检查的方法,并且在某些地方仍在使用。

  • If-else条件。在对其进行操作之前,检查函数是否返回空值。

public class SearchService {
    BookRepository bookRepository;
    
    Book searchByTitle(String title) {
        Book book = bookRepository.findByTitle(title);
        if (book == null){
          return new IllegalArgumentException("Title is invalid or not present");
        } else {
          return book;
        }
    }
}

  • 对象类。该类提供了各种静态实用方法,用于对对象进行操作或在操作前检查某些条件。

public class SearchService {
    BookRepository bookRepository;
    
    Book searchByTitle(String title) {
        Book book = bookRepository.findByTitle(title);
        if (Objects.isNull(book)){
          return new IllegalArgumentException("Title is invalid or not present");
        } else {
          return book;
        }
    }
}

为什么我们需要Optional
虽然使用if-else和Optional可以解决空值检查的问题,但它并不是一个可扩展的解决方案。随着系统的发展和新功能的加入,跟踪所有的空检查并妥善处理它们变得越来越困难。

例如,如果其他功能使用bookRepository.findByTitle(title),或者有人想使用book.getAuthor().getName()找到与书相关的作者的名字,这可能会导致一连串的空检查,使代码库更难维护和理解。

Java Optional
Java Optional是一个容器对象,用于表示一个值的存在或不存在。它是在Java 8中引入的,并提供了几个功能。

  • 处理空值。Optional用来避免NullPointerException,它明确地表示一个值的缺失。
  • 链式操作。Optional可以用来将依赖于一个值的存在的多个操作连锁起来,并在一个地方处理一个值的缺失。
  • 函数性方法。Optional类提供了功能性方法,如map、flatMap、filter和orElse、orElseGet,这些方法用于以更优雅和可读的方式处理没有值的情况。
  • 默认值:orElse和orElseGet方法可以用来在Optional为空时提供一个默认值。
  • 类型安全。Optional确保值是正确的类型,并消除了显式铸造的需要。
  • 线程安全。Optional是不可变的,因此它是线程安全的。
  • 空的Optional:它提供了一个简单的方法,通过调用Optional.empty()来创建一个空的Optional。

作为一个包装器使用
我们可以在findByTitle中用optional包裹我们的返回类型。

@Repository
public class BookRepository extends JpaRepository<Book, Long> {
    Optional<Book> findByTitle(String title);
}

这样,SearchService中的searchByTitle方法可以在使用它之前检查一个值是否存在于Optional中,防止任何潜在的空指针异常。

创建一个实例
在Java中,有几种方法来创建Optional的实例。

Optional.of(value):用给定的非空值创建一个Optional。如果传递的值是空的,它将抛出一个NullPointerException。

String value = "hello";
Optional<String> optional = Optional.of(value);


Optional.ofNullable(value):用给定的值创建一个Optional,无论它是否为空。

String value = "hello";
Optional<String> optional = Optional.ofNullable(value);

重要的是要记住,Optional是一个容器对象,它不能用来表示空值的存在,它是用来表示不存在一个值。


访问
一旦你有一个Optional的实例,有几种方法可以访问它所包含的值。

get():检索Optional中的值。如果Optional是空的,它将抛出一个NoSuchElementException。

Optional<String> optional = Optional.of("hello");
String value = optional.get();
// value = "hello"


isPresent():如果Optional包含一个值,返回true,否则返回false。

Optional<String> optional = Optional.of("hello");
if(optional.isPresent()){
    System.out.println(
"Is Present");
}


ifPresent(Consumer<T> consumer):如果有一个值存在,就用这个值调用指定的消费者,否则什么都不做。

Optional<String> optional = Optional.of("hello");
optional.ifPresent(val -> System.out.println(val));
//prints "hello"


orElse(T other):如果存在,则返回该值,否则返回other。

Optional<String> optional = Optional.empty();
String value = optional.orElse("default value"); // value = "default value"

orElseGet(Supplier<T> other):如果存在,则返回该值,否则调用其他,并返回该调用的结果。

Optional<String> optional = Optional.empty();
String value = optional.orElseGet(() -> "default value"); // value = "default value"


orElseThrow():如果存在的话,返回包含的值。否则,它将抛出作为参数提供的异常。

Optional<String> optional = Optional.empty();
String value = optional.orElseThrow(() -> new IllegalArgumentException("Value not present"));

这些方法提供了一种方法,可以安全地访问一个Optional中包含的值,而不会出现NullPointerException的风险。


需要记住的事情
在你的代码中使用Optional之前,有几件事需要考虑。

  • 代码的可读性。使用Optional会使代码更加冗长和难以阅读,尤其是在同一方法中使用多个Optional对象或它们相互嵌套的情况下。
  • 性能。使用Optional会对性能产生影响,因为它创建了额外的对象,需要更多的内存。在对性能要求很高的情况下,最好使用空值检查或其他替代品。
  • 过度使用。虽然Optional是一个防止NullPointerException的有用工具,但它不应该到处使用。过度使用Optional会使代码变得更加复杂和难以理解。重要的是,只有在它提供真正的价值时才使用它。
  • 返回类型。请记住,使用Optional的方法的返回类型与使用直接值的方法的返回类型是不同的。这可能会使我们更难理解方法的返回内容,并且在使用方法时可能会产生混乱。
  • 返回空的Optional。只有在没有找到匹配的值时,方法应该返回一个空的Optional,这样做才有意义。
  • 熟悉方法。为了充分利用Optional,你应该熟悉map、flatMap、filter和orElse、orElseGet等功能方法,这些方法用于以更优雅和可读的方式处理没有值的情况。

重要的是要权衡使用的利弊,Optional只有在它提供真正价值时才使用它,这样才能使代码更具可读性、可维护性和性能。