我们都知道处理空引用的痛苦和NullPointerExceptions的可能性。这就是Optional 的用武之地,它是一个容器对象,可能包含也可能不包含非空值,并提供各种实用方法来检查值是否存在。
在本文中,我们将讨论检查空引用的方法以及Optional 如何帮助我们避免 NPE。无论您是经验丰富的 Java 开发人员还是新手,本文都将提供有关处理代码中空引用的最佳实践的宝贵见解。
空引用问题
假设您正在构建一个图书馆管理系统。您正在使用 java 获取带有书名的书籍详细信息。
@Repository |
这里BookRepository连接到我们的数据库,SearchService处理API请求。BookRepository中的findByTitle只有在我们的数据库中存在一个标题时才会返回一个Book对象。
那么,如果没有相同书名的书,会发生什么?
该函数将返回一个空引用。
因为,这个函数可以用于其他功能,如借阅、购买、阅读和其他。如果空引用处理不当,这些功能就会中断,并可能导致NullPointerException。
在Optional之前的空值检查
在Optional之前,我们还有其他替代空参考检查的方法,并且在某些地方仍在使用。
- If-else条件。在对其进行操作之前,检查函数是否返回空值。
public class SearchService { |
- 对象类。该类提供了各种静态实用方法,用于对对象进行操作或在操作前检查某些条件。
public class SearchService { |
为什么我们需要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 |
这样,SearchService中的searchByTitle方法可以在使用它之前检查一个值是否存在于Optional中,防止任何潜在的空指针异常。
创建一个实例
在Java中,有几种方法来创建Optional的实例。
Optional.of(value):用给定的非空值创建一个Optional。如果传递的值是空的,它将抛出一个NullPointerException。
String value = "hello"; |
Optional.ofNullable(value):用给定的值创建一个Optional,无论它是否为空。
String value = "hello"; |
重要的是要记住,Optional是一个容器对象,它不能用来表示空值的存在,它是用来表示不存在一个值。
访问
一旦你有一个Optional的实例,有几种方法可以访问它所包含的值。
get():检索Optional中的值。如果Optional是空的,它将抛出一个NoSuchElementException。
Optional<String> optional = Optional.of("hello"); |
isPresent():如果Optional包含一个值,返回true,否则返回false。
Optional<String> optional = Optional.of("hello"); |
ifPresent(Consumer<T> consumer):如果有一个值存在,就用这个值调用指定的消费者,否则什么都不做。
Optional<String> optional = Optional.of("hello"); |
orElse(T other):如果存在,则返回该值,否则返回other。
Optional<String> optional = Optional.empty(); |
orElseGet(Supplier<T> other):如果存在,则返回该值,否则调用其他,并返回该调用的结果。
Optional<String> optional = Optional.empty(); |
orElseThrow():如果存在的话,返回包含的值。否则,它将抛出作为参数提供的异常。
Optional<String> optional = Optional.empty(); |
这些方法提供了一种方法,可以安全地访问一个Optional中包含的值,而不会出现NullPointerException的风险。
需要记住的事情
在你的代码中使用Optional之前,有几件事需要考虑。
- 代码的可读性。使用Optional会使代码更加冗长和难以阅读,尤其是在同一方法中使用多个Optional对象或它们相互嵌套的情况下。
- 性能。使用Optional会对性能产生影响,因为它创建了额外的对象,需要更多的内存。在对性能要求很高的情况下,最好使用空值检查或其他替代品。
- 过度使用。虽然Optional是一个防止NullPointerException的有用工具,但它不应该到处使用。过度使用Optional会使代码变得更加复杂和难以理解。重要的是,只有在它提供真正的价值时才使用它。
- 返回类型。请记住,使用Optional的方法的返回类型与使用直接值的方法的返回类型是不同的。这可能会使我们更难理解方法的返回内容,并且在使用方法时可能会产生混乱。
- 返回空的Optional。只有在没有找到匹配的值时,方法应该返回一个空的Optional,这样做才有意义。
- 熟悉方法。为了充分利用Optional,你应该熟悉map、flatMap、filter和orElse、orElseGet等功能方法,这些方法用于以更优雅和可读的方式处理没有值的情况。
重要的是要权衡使用的利弊,Optional只有在它提供真正价值时才使用它,这样才能使代码更具可读性、可维护性和性能。