Java8 Optional用法和最佳实践

发布时间 2023-09-14 11:53:21作者: Nihaorz

介绍

根据 Oracle 文档,Optional 是一个容器对象,可能包含也可能不包含非空值。Java 8 中引入它是为了解决 NullPointerException 的问题。本质上,Optional 是一个包装类,其中包含对其他对象的引用。在这种情况下,对象只是指向内存位置的指针,它也可以指向任何内容。另一种看待它的方式是提供类型级解决方案来表示Optional而不是空引用。

在 Optional 之前

在 Java 8 之前,程序员将返回 null 而不是Optional。有几个

这种方法的缺点。一是没有明确的方法来表达 null 可能是一个特殊值。相比之下,返回一个Optional在API中是一个明确的声明,表明其中可能没有值。如果我们想确保不会出现空指针异常,那么我们需要对每个引用进行显式空检查,如下所示,我们都同意这是很多样板文件。

// Life before Optional
private void getIsoCode( User user){
    if (user != null) {
        Address address = user.getAddress();
        if (address != null) {
            Country country = address.getCountry();
            if (country != null) {
                String isocode = country.getIsocode();
                if (isocode != null) {
                    isocode = isocode.toUpperCase();
                }
            }
        }
    }
}

为了简化这个过程,让我们看看如何使用Optional类,从创建和验证实例,到使用它提供的不同方法,并将其与返回相同类型的其他方法结合起来,后者是可选谎言的真正力量。

Optional 的特性

Optional 提供了大约 10 个方法,我们可以使用它们来创建和使用 Optional,下面我们将了解它们的使用方法。

创建一个 Optional

Optional 有三种实例创建方法。

  1. static Optional empty()

返回一个空的 Optional 实例。

// Creating an empty optional
Optional<String> empty = Optional.empty();

返回一个空的 Optional 实例。此 Optional 不存在任何值。尽管这样做可能很诱人,但请避免通过与 {==} 与 {Option.empty()} 返回的实例进行比较来测试对象是否为空。不能保证它是单例。相反,请使用 {isPresent()}。

  1. static Optional of(T value)

返回具有指定的当前非空值的 Optional。

// Creating an optional using of
String name = "java";
Optional<String> opt = Optional.of(name);

静态方法需要一个非空参数,否则会抛出空指针异常。那么,如果我们不知道参数是否为空,那就是当我们使用下面描述的 ofNullable 时。

  1. static Optional ofNullable(T value)

如果非空,则返回描述指定值的 Optional ,否则返回空Optional。

// Possible null value
Optional<String> optional = Optional.ofNullable(name());

private String name(){
    String name = "Java";
    return (name.length() > 5) ? name :  null;
}

通过这样做,如果我们传入一个空引用,它不会抛出异常,而是返回一个空的Optional对象:

好的,这些是动态或手动创建选项的三种方法。下一组方法用于检查值是否存在。

  1. boolean isPresent()

如果存在值则返回 true,否则返回 false。如果包含的对象不为 null,则返回 true,否则返回 false。在对对象执行任何其他操作之前,通常首先对可选对象调用此方法。

//ispresent
Optional<String> optional1 = Optional.of("javaone");
if (optional1.isPresent()){
    //Do something, normally a get
}
  1. boolean isEmpty()

如果存在值则返回 false,否则返回 true。这与 isPresent 相反,仅在 Java 11 及更高版本中可用。

// isempty
Optional<String> optional1 = Optional.of("javaone");
if (optional1.isEmpty()){
    //Do something
}
  1. void ifPresent(Consumer<? super T> consumer)

如果存在值,则使用该值调用指定的使用者,否则不执行任何操作。

如果您是 Java 8 的新手,那么您可能想知道什么是消费者,简单来说,消费者是一种接受参数但不返回任何内容的方法。当使用 ifPresent 时,这看起来就像一石二鸟。我们可以进行值存在检查并使用一种方法执行预期操作,如下所示。

// ifpresent
Optional<String> optional1 = Optional.of("javaone");
opt.ifPresent(s -> System.out.println(s.length()));

Optional 提供了另一组用于获取Optional的方法。

  1. T get()

如果此 Optional 中存在值,则返回该值,否则抛出 NoSuchElementException。总而言之,我们想要的是存储在Optional中的值,我们可以通过简单地调用get()来获取它。然而,当值为 null 时,此方法会引发异常,此时 orElse() 方法可以发挥作用。

// get
Optional<String> optional1 = Optional.of("javaone");
if (optional1.isPresent()){
    String value = optional1.get();
}
  1. T orElse(T other)

如果存在则返回该值,否则返回其他值。

orElse() 方法用于检索包装在Optional实例中的值。它采用一个作为默认值的参数。orElse() 方法返回包装值(如果存在),否则返回其参数:

// orElse
String nullName = null;
String name = Optional.ofNullable(nullName).orElse("default_name");

好像这还不够,可选类继续提供另一种获取值的方法,即使它的 null 称为 orElseGet()

  1. T orElseGet(Supplier<? extends T> other)

如果存在则返回该值,否则调用 other 并返回该调用的结果。

orElseGet() 方法与 orElse() 类似。但是,如果Optional不存在,它不会采用返回值,而是采用供应商功能接口,该接口被调用并返回调用的值:

// orElseGet
String name = Optional.ofNullable(nullName).orElseGet(() -> "john");

那么 orElse() 和 orElseGet() 有什么区别

乍一看,这两种方法似乎具有相同的效果。然而,事实并非如此。让我们创建一些示例来突出显示两者之间行为的相似性和差异。

首先,让我们看看当对象为空时它们的行为如何:

String text = null;

String defaultText = Optional.ofNullable(text).orElseGet(this::getDefaultValue);


defaultText = Optional.ofNullable(text).orElse(getDefaultValue());


public String getDefaultValue() {
    System.out.println("Getting Default Value");
    return "Default Value";
}

在上面的示例中,我们将空文本包装在Optional对象内,并尝试使用两种方法中的每一种来获取包装的值。副作用如下:

Getting default value...
Getting default value...

在每种情况下都会调用默认方法。碰巧,当包装值不存在时,orElse() 和 orElseGet() 的工作方式完全相同。

现在让我们运行另一个测试,其中该值存在,并且理想情况下,甚至不应该创建默认值:

String text = "Something";
System.out.println("Using orElseGet:");
String defaultText = Optional.ofNullable(text).orElseGet(this::getDefaultValue);

 System.out.println("Using orElse:");
defaultText = Optional.ofNullable(text).orElse(getDefaultValue());


public String getDefaultValue() {
    System.out.println("Getting Default Value");
    return "Default Value";
}

在上面的示例中,我们不再包装空值,其余代码保持不变。现在让我们看一下运行这段代码的副作用:

Using orElseGet:
Using orElse:
Getting default value...

请注意,当使用 orElseGet() 检索包装的值时,甚至不会调用默认方法,因为包含的值存在。

但是,当使用 orElse() 时,无论包装值是否存在,都会创建默认对象。因此,在这种情况下,我们刚刚创建了一个从未使用过的冗余对象。

在这个简单的示例中,创建默认对象没有显着的成本,因为 JVM 知道如何处理此类对象。然而,当诸如 default 之类的方法必须进行 Web 服务调用甚至查询数据库时,成本就变得非常明显。

使用 Optional 的最佳实践

就像编程语言的任何其他功能一样,它可以被正确使用,也可以被滥用。为了了解使用可选类的最佳方式,需要了解以下内容:

1.它试图解决什么问题

Optional 是一种减少 Java 系统中空指针异常数量的尝试,方法是增加构建更具表现力的 API 的可能性,以解决有时返回值丢失的可能性。

如果 Optional 从一开始就存在,大多数库和应用程序可能会更好地处理丢失的返回值,从而减少空指针异常的数量和总体错误的总数。

2.它不试图解决什么问题

可选并不是一种避免所有类型空指针的机制。例如,方法和构造函数的强制输入参数仍然需要测试。

与使用 null 时一样,Optional 无助于传达缺失值的含义。类似地,null 可以表示许多不同的含义(未找到值等),缺少的Optional也可以表示许多不同的含义。

该方法的调用者仍然需要检查该方法的javadoc以了解缺少的Optional的含义,以便正确处理它。

同样,通过类似的方式,可以在空块中捕获已检查的异常,没有什么可以阻止调用者调用 get() 并继续前进。

3.何时使用

Optional的预期用途主要是作为返回类型。获取此类型的实例后,您可以提取该值(如果存在)或提供替代行为(如果不存在)。

Optional 类的一个非常有用的用例是将其与流或其他返回Optional的方法相结合,以构建流畅的 API。请参阅下面的代码片段

User user = users.stream().findFirst().orElse(new User("default", "1234"));

4.什么时候不使用它

a) 不要将其用作类中的字段,因为它不可序列化

如果您确实需要序列化包含Optional值的对象,Jackson库提供了将Optional视为普通对象的支持。这意味着 Jackson 将空对象视为 null,将具有值的对象视为包含该值的字段。此功能可以在 jackson-modules-java8 项目中找到。

b) 不要将其用作构造函数和方法的参数,因为这会导致代码不必要地复杂。

User user = new User("john@gmail.com", "1234", Optional.empty());

重要注意事项

这是一门以价值观为基础的课程;在Optional实例上使用身份敏感操作(包括引用相等(==)、身份哈希码或同步)可能会产生不可预测的结果,应避免。

原文链接

https://medium.com/@hopewellmutanda/java-8-optional-usage-and-best-practices-975c41d66822