【译】你的Java8 Optional指南

原文地址

Java程序员面临的最常见异常之一是NullPointerException。 JVM会在运行时将该异常作为运行时异常抛出。

众所周知,当程序需要一个对象但是却找到一个null值时,就会抛出NullPointerException。对空指针异常进行处理是Java程序员最容易忽略的场景之一。

在继续执行常规业务逻辑之前,需要在应用程序中处理空指针异常,以避免在运行时产生空指针异常。这导致了不必要的空指针检测代码的存在。

为了在Java中处理这些用于进行空指针检测的模板代码,所以Java 8中引入了新类型Optional<T>

在没有引入Optional之前会有什么问题?

据Oracle说,Java 8中引入的Optional作为一个容器类型,用于包装值不存在或为null的情况。 Optional是java.util包中一个具有final修饰符的类。

我们来看看没有Optional会遇到哪些问题。

假设在我们的程序中具有以下方法。此方法使用员工的ID从数据库中检索员工的详细信息:

Employee findEmployee(String id) {
 ...
};
复制代码

假设在使用上述方法时所提供的ID在数据库中不存在。然后,该方法将返回null值。现在,如果我们已经编写了下面的代码:

Employee employee = findEmployee("1234");
System.out.println("Employee's Name = " + employee.getName());
复制代码

上面的代码将在运行时抛出NullPointerException异常,因为程序员在使用该值之前没有对它进行空指针检测。

Java 8的Optional提供了怎样的解决方案?

现在,让我们看看Java 8引入的Optional类型将如何解决上述问题并帮助消除NullPointerException异常。

以下是上述代码修改过后的代码:

Optional<Employee> findEmployee(String id) {
 ...
};
复制代码

在上面的代码中,我们将返回类型修改为Optional<Employee>来向调用者表明与给定ID的对应的员工可能不存在。

现在,需要在调用方中明确表达这一事实。

调用者应该编写如下代码:

Optional <Employee> optional = findEmployee("1234");
optional.ifPresent(employee -> {
 System.out.println("Employee name is " + employee.getName());
})
复制代码

你可以看到我们在上述代码的第一行中创建了一个Optional对象。现在,我们可以使用Optional对象上面的各种方法。

以上代码段中的ifPresent()方法仅在员工存在的情况下才调用提供的lambda表达式。 如果员工不存在,则什么事都不会做。

Optional的优点

以下列出的是使用Optional的一些优点:

  • 程序在运行时不会抛出NullPointerException异常。
  • 在程序中不再需要空指针检测。
  • 容易开发干净整洁的API。

Optional类的方法

Methods Description
public static Optional empty() This method returns an empty Optional object. No value is present for this Optional.
public static Optional of(T value) This method returns an Optional with the specified value that is not null.
public static Optional ofNullable(T value) This method returns an Optional describing the specified value if the value is non-null; otherwise, it returns an empty Optional.
public T get() If a value is present in this Optional, then it returns the value. Otherwise, it throws NoSuchElementException.
public boolean isPresent() This method returns a true value if there is a value present. Otherwise, it returns false.
public void ifPresent(Consumer<? super T> consumer) If a value is present, then the consumer with the provided value is invoked. Otherwise, it does nothing.
public Optional filter(Predicate<? super T> predicate) If a value is present and it also matches the given predicate, then it returns an Optional describing the value. Otherwise, it returns an empty Optional.
public Optional map(Function<? super T,? extends U> mapper) If a value is present, then the mapping function is applied, and if the result is not a null value, then it returns an Optional describing the result. Otherwise, it returns an empty Optional.
public Optional flatMap(Function<? super T,Optional mapper) If the value is present, then it applies the provided Optional-bearing mapping function to it and it returns that result. Otherwise, it returns an empty Optional.
public T orElse(T other) This method returns the value if present; otherwise, it returns other.
public T orElseGet(Supplier<? extends T> other) This method returns the value if present. Otherwise, it invokes other and returns the result of the invocation.
public T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X extends Throwable If the value is present, this method returns the contained value. Otherwise, it throws an exception to be created by the provided supplier.
public boolean equals(Object obj) This method is used for indicating whether some other object is “equal to” this Optional or not.
public int hashCode() This method returns the hash code value of the present value if it exists. Otherwise, it returns 0 (zero) .
public String toString() This method is simply used to return a non-empty string representation of the Optional, which is suitable for debugging.

创建一个Optional对象

在本节中,我们将研究用于创建Optional对象的方法:

1. 创建一个包含空值的Optional对象

下面的代码演示了如何创建一个具有空值的Optional对象。它用来表示值为null的情况。

Optional <Employee> employee = Optional.empty();
复制代码

2. 创建一个具有非空值的Optional对象

下面的代码展示了如何创建一个具有非空值的Optional对象。

Employee employee = new Employee("1234", "TechBlogStation");
Optional <Employee> optional = Optional.of(employee);
复制代码

请注意,如果为Optional.of()的参数提供了null,则它将立即抛出NullPointerException异常,所以创建Optional对象将会失败。

检查Optional对象中是否存在非空值

现在,让我们学习通过不同的方法检查Optional对象中是否存在非空值:

1. isPresent()方法

如果Optional对象中存在非空值,则方法isPresent()返回true,否则,它返回false。

if (optional.isPresent()) {
  // optional中有值
  System.out.println("Value - " + optional.get());
} else {
  // optional中没有值
  System.out.println("Optional is empty");
}    

复制代码

2.ifPresent()方法

在方法ifPresent()中,我们将传递一个Customer函数过去。仅当Optional对象中存在非空值时,才会执行该Consumer函数。

如果Optional对象中存放的是空值,则不执行任何操作:

optional.ifPresent(value -> {   
  System.out.println("Value present - " + value); 
});
复制代码

在上面的代码中,我们提供了lambda函数作为ifPresent()方法的参数。

使用get()方法从Optional中取出值

Optional的get()方法仅用于从Optional对象取出值。如果Optional对象中保存的是一个空值,则将引发NoSuchElementException异常。

Employee employee = optional.get()
复制代码

在上述代码中,如果optional对象中是个保存的是空值,则会抛出异常,因此建议在使用get()方法之前,我们应该首先检查optional中是否有值。

使用Optional的orElse()方法返回默认值

如果Optional对象中保存的是空值,则返回调用orElse()方法所传入的参数作为默认值。如果其中保存的是非空值,则返回其中保存的值。

请参见下面的示例:

// 使用三目表达式返回默认值
User finalEmployee = (employee != null) ? employee : new Employee("0", "Unknown Employee");
复制代码

现在,使用Optional的orElse()方法实现与上面相同的逻辑:

// 使用orElse()方法返回默认值
User finalEmployee = optional.orElse(new Employee("0", "Unknown Employee"));
复制代码

使用Optional的orElseGet()方法返回默认值

我们已经知道,如果Optional对象为空,则方法orElse()直接返回调用时传入的参数作为默认值,而orElseGet()方法接受一个 Supplier作为参数,并且当Optional对象中保存的是空值时调用Supplier。

Supplier返回的结果将成为Optional的默认值。

User finalEmployee = optional.orElseGet(() -> {
 return new Employee("0", "Unknown Employee");
});
复制代码

如果Optional中存放的是空值,则抛出异常

如果Optional对象为空,则可以使用orElseThrow()方法抛出一个异常。

它可以用于REST API中指定的对象不存在的情况。您可以使用此方法抛出自定义异常,例如ResourceNotFound()等:

@GetMapping("/employees/{id}")
public User getEmployee(@PathVariable("id") String id) {
 return employeeRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Employee not found with id " + id););
}
复制代码

使用Optional的Filter()方法过滤值

假设您有一个Employee的Optional对象。现在,你正在检查员工的性别并相应地调用一个函数。

下面是这样做的旧方法:

if(employee != null && employee.getGender().equalsIgnoreCase("MALE")) {
	// 调用函数
}
复制代码

现在,让我们看看如何使用Optional的filter()方法来达到此目的:

optional.filter(user -> employee.getGender().equalsIgnoreCase("MALE")) .ifPresent(() -> {
    // 你的函数
})
复制代码

filter()方法将一个谓词函数作为参数。如果Optional对象中包含一个非空值并且该值与提供的谓词匹配,则此方法将返回一个包含该值的Optional对象。

否则,此方法返回一个包含空值的Optional对象。

使用map()提取与转换值

假设我们有一个场景,我们要提取雇员的地址,还要根据指定条件打印出该地址。

考虑以下示例:

我们在Employee类中有getAddress()方法:

Address getAddress() {
	return this.address;
}
复制代码

以下是实现上述需求的典型方式:

if (employee != null) {
    Address address = employee.getAddress();
    if (address != null && address.getCountry().equalsIgnoreCase("USA")) {
        System.out.println("Employee belongs to USA");
    }
}
复制代码

现在看看如何使用Optional的map()方法实现一样的功能:

userOptional.map(Employee::getAddress).filter(address -> address.getCountry().equalsIgnoreCase("USA")).ifPresent(() -> {
 System.out.println("Employee belongs to USA");
});
复制代码

与以前的方法相比,以上代码可读性强、简洁与高效。

让我们更详细地分析一下代码:


// 使用map()方法提取地址
Optional<Address> addressOptional = employeeOptional.map(Employee::getAddress) 

// 过滤出来自USA的员工
Optional<Address> usaAddressOptional = addressOptional.filter(address -> address.getCountry().equalsIgnoreCase("USA")); 

// 如果员工来自USA则输出一些信息
usaAddressOptional.ifPresent(() -> {
  System.out.println("Employee belongs to USA");
});
复制代码

在上述代码段中,出现以下任意一种情况,方法map()返回一个包含空值的Optional对象:

  1. 如果employeeOptional中不存在员工。
  2. 如果员工存在,但方法getAddress()返回null。

否则,返回包含员工地址的Optional<Address>对象。

使用flatMap()级联Optional

现在,让我们再次考虑上述与map()方法相关的示例。

你可以看到即使Employee地址可以为null,我们也没有使用Optional<Address>作为getAddress()方法的返回类型。

如果getAddres()方法的返回类型为Optional<Address>,则下面这一行代码将会出现问题:

Optional<Address> addressOptional = employeeOptional.map(Employee::getAddress)
复制代码

由于getAddress()方法返回Optional <Address>,因此employeeOptional.map()的返回类型是Optional<Optional<Address>>

下面是对于这种情况的演示:

Optional<Optional<Address>> addressOptional = employeeOptional.map(Employee::getAddress)
复制代码

这种情况下,我们嵌套了Optional,但是我们不希望这样做,所以我们现在可以使用flatMap()方法解决这个问题:

Optional<Address> addressOptional = employeeOptional.flatMap(Employee::getAddress)
复制代码

请注意,如果您的传递给map()方法的函数返回一个Optional对象,则应该使用flatMap()方法替换掉map()方法,从而获取到展开后的结果。

总结

我们已经学习了Java 8中的Optional是什么,它的优点以及在Java中通过使用Optional解决问题。此外,通过一些示例,我们能够更好地明白Java 8中Optional类的一些方法。

谢谢阅读!

进一步阅读

Java 8 Optional Uses and Best Practices

26 Reasons Why Using Optional Correctly Is Not Optional

Java 8 Optional: Handling Nulls Properly

猜你喜欢

转载自juejin.im/post/5e2d955af265da3e006b46df