使用Java Optional类的最佳实践 - oracle


请遵循以下十二种最佳实践,以保护您的应用程序免受丑陋的空指针异常的侵扰,并使您的代码更具可读性和简洁性。
每个认真的Java开发人员或架构师都曾经听说过或经历过NullPointerException异常的滋扰。Java工程师一直致力于解决该null问题很长时间,在Java 8中,添加了一个称为的新类型Optional<T>,Optional原本是一种返回类型,当与流(或return的方法Optional)组合以构建流畅的API时使用。此外,它旨在帮助开发人员null正确处理引用。
下面谈谈如何避免使用Optional反模式? 或者说不正确用法:
 
1:切勿将null分配给可选变量。
有时,当开发人员正在处理数据库以寻找Employee时,他们设计了return的方法Optional<Employee>。我发现null如果数据库没有返回任何结果,开发人员仍会返回,例如:

public Optional<Employee> getEmployee(int id) {
 // perform a search for employee 
  Optional<Employee> employee = null;
// in case no employee
   return employee; 
}

上面的代码不正确,应该完全避免。要更正它,您应该将第3行替换为以下行,该行Optional以空初始化Optional:
Optional<Employee> employee = Optional.empty();

 
2:不要直接调用get()

Optional<Employee> employee = HRService.getEmployee();
Employee myEmployee = employee.get();

您是否猜到“Employee”Optional里面是空着,所以get()直接打电话会抛出一个“ java.util.NoSuchElementException?”。您应该始终首先使用该isPresent()方法检查值的存在,如下所示:
if (employee.isPresent()) {
    Employee myEmployee = employee.get();
    ... // do something with "myEmployee"
} else {
    ...
// do something that doesn't call employee.get()
}

请注意,上面的代码是样板代码,现实中这样做是不可取的。接下来,您将看到很多优雅的呼叫isPresent()/get()对替代方法。
 
3. 避免使用isPresent()和get()对来设置和返回值。

public static final String DEFAULT_STATUS = "Unknown";
...
public String getEmployeeStatus(long id) {
    Optional<String> empStatus = ... ;
    if (empStatus.isPresent()) {
        return empStatus.get();
    } else {
        return DEFAULT_STATUS;
    }
}

只需使用orElse()替换isPresent()和get()配对,如以下:
public String getEmployeeStatus(long id) {
    Optional<String> empStatus = ... ;
    return empStatus.orElse(DEFAULT_STATUS); 
}

这里要考虑的一个非常重要的注意事项是可能的性能损失:orElse()无论可选值的存在与否,总是会评估返回的值。因此,这里的规则是orElse()在您已经预先构造了值并且不使用昂贵的计算值时使用。
 
4. 请勿使用orElse()返回计算值。
避免使用orElse()返回计算值,因为这会降低性能。考虑以下代码片段:

Optional<Employee> getFromCache(int id) {
    System.out.println("search in cache with Id: " + id);
   
// get value from cache
}

Optional<Employee> getFromDB(int id) {
    System.out.println(
"search in Database with Id: " + id);    
   
// get value from database
}

public Employee findEmployee(int id) {        
    return getFromCache(id)
            .orElse(getFromDB(id)
                    .orElseThrow(() -> new NotFoundException(
"Employee not found with id" + id)));}

首先,代码尝试从缓存中获取具有给定ID的Employee,如果该Employee不在缓存中,则尝试从数据库中获取Employee。然后,如果员工不在缓存或数据库中,则代码将引发一个NotFoundException。如果运行此代码,而雇员在缓存中,则会打印以下内容:
Search in cache with Id: 1
Search in Database with Id: 1

即使将Employee从缓存中返回,数据库查询仍然被调用。很昂贵,对不对?取而代之的是,我将使用orElseGet(Supplier<? extends T> supplier),就像,orElse()但有一个区别:如果theOptional为空,则orElse()直接返回默认值,而orElseGet()允许您传递Supplier仅当theOptional为空时才调用的函数。这对性能非常有用。

public Employee findEmployee(int id) {        
    return getFromCache(id)
        .orElseGet(() -> getFromDB(id)
            .orElseThrow(() -> {
                return new NotFoundException("Employee not found with id" + id);
            }));
}

这次,您将获得所需的内容并提高了性能:该代码将仅打印以下内容:

Search in cache with Id: 1

不要考虑使用isPresent()和get()配对,因为它们并不雅致。
orElseThrow()方法自Java 10起就存在。
 
5.如果仅当存在Optional值时才想执行操作,请不要使用isPresent()-get()。

Optional<String> confName = Optional.of("CodeOne");
if(confName.isPresent())
    System.out.println(confName.get().length());

只需将上面的第2行和第3行替换为一行,如下所示:
confName.ifPresent( s -> System.out.println(s.length()));

ifPresent()方法不返回任何内容,并且自Java 8起就存在。

6.
如果不存在值,则不要使用isPresent()-get()执行基于空的操作。
开发人员有时会编写代码,如果存在Optional值,该代码会执行某些操作,但如果不存在,则执行基于空的操作,如下所示:
Optional<Employee> employee = ... ;
 if(employee.isPresent()) {
    log.debug("Found Employee: {}" , employee.get().getName());
 } else {
    log.error(
"Employee not found");
 }

请注意,ifPresentOrElse()就像是ifPresent(),唯一的区别是,它涵盖了else分支为好。因此,您可以将第2行到第6行替换为:

employee.ifPresentOrElse(
emp -> log.debug("Found Employee: {}",emp.getName()), 
() -> log.error(
"Employee not found"));

ifPresentOrElse()方法自Java 9起就存在。
 
7.不存在任何值时,返回另一个Optional。
在某些情况下,如果Optional中有值存在,则返回一个Optional描述值的描述;否则,返回Optional由供应功能产生的产品。避免执行以下操作:
Optional<String> defaultJobStatus = Optional.of("Not started yet.");
public Optional<String> fetchJobStatus(int jobId) {
    Optional<String> foundStatus = ... ;
// fetch declared job status by id
    if (foundStatus.isPresent())
        return foundStatus;
    else
        return defaultJobStatus; 
}

不要过度使用orElse()ororElseGet()方法来完成此操作,因为这两个方法都返回一个未包装的值。因此,也请避免执行以下操作:
public Optional<String> fetchJobStatus(int jobId) {
    Optional<String> foundStatus = ... ; // fetch declared job status by id
    return foundStatus.orElseGet(() -> Optional.<String>of(
"Not started yet."));
}

完美而优雅的解决方案是使用该 (Supplier<? extends Optional<? extends T>> supplier)方法,如下所示:
 public Optional<String> fetchJobStatus(int jobId) {
    Optional<String> foundStatus = ... ; // fetch declared job status by id
    return foundStatus.or(() -> defaultJobStatus);
 }

或者,即使没有defaultJobStatus在开始时定义可选的内容,也可以使用以下代码替换第3行中的代码:
return foundStatus.or(() -> Optional.of("Not started yet."));

如果提供函数为或产生结果,则or()抛出NullPointerExceptionnull异常。从Java 9开始就存在此方法。
 
8.无论是否为空,都获取Optional的状态。
从Java 11开始,您可以Optional使用isEmpty()方法直接检查an是否为空。

public boolean isMovieListEmpty(int id){
    Optional<MovieList> movieList = ... ;
    return !movieList.isPresent();
 }

您可以将第3行替换为以下行,以使代码更具可读性:
return movieList.isEmpty();

 
9.不要过度使用Optional。
有时,开发人员倾向于过度使用他们喜欢的东西,而Optional类就是其中之一。
1 public String fetchJobStatus(int jobId) {
2    String status = ... ; // fetch declared job status by id
3    return Optional.ofNullable(status).orElse(
"Not started yet.");
4 }

通过使用以下更清晰的代码行替换第3行,可以很简单:
return status == null ? "Not started yet." : status;