Java | Optional的12种实践建议

摘要:
您应该用以下行替换第3行,并初始化OptionOptionalEmployee=Optional。empty()和可选的empty(();可选是可以存储值的容器。用null初始化它是无用的。可选Employee=HRService.getEmployee();EmployeemyEmployee=employee.get();您猜到Optional的“employee”可能为空吗,直接调用get()可能会抛出一个java。util。NoSuchElementException?因此,可以用以下简洁的代码行替换第4行到第8行:4方法。援引APInote:orElse()方法适用于Java8,上面讨论了在使用Optional时如何避免空引用。下面讨论了可选中设置和返回数据的不同方法。

本篇翻译Mohamed Taman的Optional12种实践建议

Recipe 1: 不要给optional 变量赋null值

有时候,当开发人员在处理数据库以查询一个employee时,会设计一个方法来返回Optional;如果没有结果返回从数据库,有一些开发人员仍然返回null,,例如:

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

上面的代码是不正确的,应该完全避免它。应该用下面的行替换第3行,使用Optional自带的empty()来初始化Option

Optional<Employee> employee = Optional.empty();

Optional是一个可以保存值的容器,用null初始化它是没有用的。
API note: empty()方法适用Java8及以上

Recipe 2: 不要直接调用get()

考虑下面的代码段。有什么问题吗?

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

是否猜到Optional的“employee”可能为空,直接调用get()可能抛出java.util.NoSuchElementException?如果是的话,你是对的。如果你认为调用get()会让你一天过得很愉快,那你就错了。首先应该使用isPresent()方法检查值是否存在,如下所示:

if (employee.isPresent()) {
    Employee myEmployee = employee.get();
    ... // do something with "myEmployee"
} else {
    ... // do something that doesn't call employee.get()
}

注意,上面的写法只是样例,并不可取,下面会有几种代替isPresent()/get()方法的
API note: isPresent()/get()方法适用Java8及以上

Recipe 3:使用Optional时,当Optional值为空引用时不要直接使用null

在某些情况下,Optional的值为null,此时不要直接使用null,而是应该使用orElse(null)
考虑下面调用方法类的反射API的invoke()方法的示例。它在运行时调用该方法。如果调用的方法是静态的,则第一个参数为null;否则,它传递包含类实例的方法。

1 public void callDynamicMethod(MyClass clazz, String methodName) throws ... {
2    Optional<MyClass> myClass = clazz.getInstance();
3    Method method = MyClass.class.getDeclaredMethod(methodName, String.class);
4    if (myClass.isPresent()) {
5        method.invoke(myClass.get(), "Test");
6    } else {
7        method.invoke(null, "Test");
8    }
9 }

通常,应该避免使用orElse(null),但是在这种情况下,使用orElse(null)比使用上面的代码更可取。因此,您可以用以下简洁的代码行替换第4行到第8行:

4    method.invoke(myClass.orElse(null), "Test");

API note: orElse()方法适用Java8及以上

上面讨论在使用Optional时如何避免null引用问题。下面讨论在Optional中设置和返回数据的不同方法。

Recipe 4: 避免使用isPresent()/get()去操作value

考虑下面的代码。你能改变什么使它更优雅和有效?

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;
    }
}

与Recipe #3相似,使用orElse()代替isPresent()/get(),如下:

public String getEmployeeStatus(long id) {
    Optional<String> empStatus = ... ;
    return empStatus.orElse(DEFAULT_STATUS); 
}

这里需要考虑的一个非常重要的问题-性能损失:无论optional的value是否存在,orElse()返回的value始终会被计算。因此,这里的规则是,当您已经有预先构造的值并且不使用昂贵的计算值时,使用orElse()。
API note: orElse()方法适用Java8及以上

Recipe 5: 不要使用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。如果您运行这段代码,并且雇employee在缓存中,则会打印以下内容:

Search in cache with Id: 1
Search in Database with Id: 1

即使employee将从缓存中返回,数据库查询仍然被调用。这很贵,对吧?相反,我将使用orElseGet(Supplier<? extends T> supplier) 这个有点像 orElse() 但有点不同, orElseGet()允许当optional是empty时候去调用你定义的supplier 方法,这有利于提高性能。
现在考虑使用orElseGet(),重构上面的代码

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       

API note: orElseGet()方法适用Java8及以上

Recipe 6: 在没有值的条件下抛出 exception

在某些情况下,您需要抛出异常,以表明某个值不存在。这通常发生在您开发与数据库或其他资源交互的服务时。使用Optional,很容易做到这一点。考虑下面的例子:

public Employee findEmployee(int id) {        
    var employee = p.getFromDB(id);
    if(employee.isPresent())
        return employee.get();
    else
        throw new NoSuchElementException();
}

优雅实现:

public Employee findEmployee(int id) {        
    return getFromDB(id).orElseThrow();
}

API note:orElseThrow()适用 Java 10. 如果使用 Java 8 or 9, 参考recipe #7.

Recipe 7: 如何在没有值的时候显示抛出异常?

在Recipe #6,只能抛出一种隐式的异常-NoSuchElementException。但是这样的异常不足以向客户报告问题的描述性和相关性信息。如果你还记得的话,Recipe #5使用了orElseThrow(Supplier<? extends X> exceptionSupplier)方法,如果Optional没有值,orElseThrow方法能够抛出显示传给它的异常。
重构下面的代码片段

@GetMapping("/employees/{id}")
public Employee getEmployee(@PathVariable("id") String id) {
    Optional<Employee> foundEmployee = HrRepository.findByEmployeeId(id);
    if(foundEmployee.isPresent())
        return foundEmployee.get();
    else
        throw new NotFoundException("Employee not found with id " + id);
}

重构后:

@GetMapping("/employees/{id}")
public Employee getEmployee(@PathVariable("id") String id) {
    return HrRepository
    .findByEmployeeId(id)
    .orElseThrow(
        () -> new NotFoundException("Employee not found with id " + id));
}

此外,如果你只对抛出空异常感兴趣,像这样:

return status.orElseThrow(NotFoundException::new);

API note:如果传入null参数给orElseThrow()方法,当没有值的时候会抛出NullPointerException。orElseThrow(Supplier<? extends X> exceptionSupplier)适用java8及以上

Recipe 8: 如果希望仅在Optional存在可选值时执行操作,则不要使用isPresent()-get()。

有时, 希望仅在Optional存在可选值存在时执行操作,而在不存在可选值时不执行操作。这时应当使用ifPresent(Consumer<? super T> action)方法。下面的代码应当避免

1 Optional confName = Optional.of("CodeOne"); 2 if(confName.isPresent()) 3 System.out.println(confName.get().length());
使用ifPresent() 只需将上面第2、3行替换为一行,如下:

confName.ifPresent( s -> System.out.println(s.length()));

API note:ifPresent()方法适用Java8及以上

Recipe 9: 如果值不存在,不要使用 isPresent()-get()操作

有时候,开发人员会编写针对Optional value存在和不存在的代码,如下:

1 Optional<Employee> employee = ... ;
2 if(employee.isPresent()) {
3    log.debug("Found Employee: {}" , employee.get().getName());
4 } else {
5    log.error("Employee not found");
6 }

注意,ifPresentOrElse()类似于ifPresent(),唯一的区别是它也覆盖了else分支。因此,您可以将第2行到第6行替换为:

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

API note:ifPresentOrElse()方法适用Java9及以上

Recipe 10:当 Optional不存在值时返回另一个Optional。

在某些情况下,当Optional value是存在情况下会返回一个描述该value的Optional,否则,会返回一个由supplying function生成的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()或orElseGet()方法来完成此操作,因为这两个方法都返回一个未封装的值。
所以也不要这样做:

public Optional<String> fetchJobStatus(int jobId) {
    Optional<String> foundStatus = ... ; // fetch declared job status by id
    return foundStatus.orElseGet(() -> Optional.<String>of("Not started yet."));
}

完美而优雅的解决方案是使用or (Supplier<? extends Optional<? extends T>> supplier)方法,像下面:

1 public Optional<String> fetchJobStatus(int jobId) {
2    Optional<String> foundStatus = ... ; // fetch declared job status by id
3    return foundStatus.or(() -> defaultJobStatus);
4 }

或者 在开始时没有定义defaultJobStatus可选,你也可以用以下代码替换第3行代码:

return foundStatus.or(() -> Optional.of("Not started yet."));

API note:or()在supplying function是null或者结果为null值时会抛出NullPointerException 。or()方法适用Java9及以上

Recipe 11: 在不关注Optional Value是否为null情况下获取该Optional的状态

自Java11起,判断Optional是否为空可以使用isEmpty()方法,isEmpty()会在Optional为空时返回true。所以,可以代替下面的代码片段:

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

可以用下面的行替换第3行,使代码更易读:

return movieList.isEmpty();

API note:isEmpty()方法适用Java11及以上

Recipe 12: 不要过度使用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;

免责声明:文章转载自《Java | Optional的12种实践建议》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇SAP Study Notes: BW Queriy-Variables(变量)Linux系统建立Nor Flash分区下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

HashMap实现缓存

package com.cache; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; pub...

JAVA 使用Comparator接口实现自定义排序

1、原则 Comparator接口可以实现自定义排序,实现Comparator接口时,要重写compare方法: int compare(Object o1, Object o2) 返回一个基本类型的整型 如果要按照升序排序,则o1 小于o2,返回-1(负数),相等返回0,01大于02返回1(正数) 如果要按照降序排序,则o1 小于o2,返回1(正数),相...

windows下安装配置phpjavabridge,PHP调用自己的JAVA文件

方法一:(推荐方法 ) 使用php/java桥 JavaBridge.jar  转自:http://zhengdl126.iteye.com/blog/418574http://sourceforge.net/projects/php-java-bridge http://mirror.optus.net/sourceforge/p/ph/php-java...

使用curses管理基于文本的屏幕--(八)

CD管理程序现在我们已经了解了curses所提供了功能,我们可以继续开发我们的例子程序。在这里所展示是一个使用curses库的C语言版本。他提供了一些高级的特性,包括更为清晰的屏幕信息显示以及用于跟踪列表的滚动窗口。完整的程序共页长,所以我们将其分为几部分,在每一部分中介绍一些函数。试验--一个新的CD管理程序1 首先,我们包含所有的头文件以及一些全局常量...

jsp页面渲染

1.jsp    1.jsp脚本和注释     1)<%java代码%> --------------内部的java代码翻译到service方法的内部     2)<%=java变量或表达式%>  ----------会被翻译成service 方法内部 out.print()     3)<%! java代码%> ---...

DataSanp App与Rest, WebBroker App的区别

DataSanp App与Rest, WebBroker App的区别  datasnap server :选择这一项,我们得到的将是一个独立EXE的三层服务器应用程序(TCP及HTTP两种模式)          Tokyo 10.2.2,加上HTTPS,共3中通讯协议。     ServerContainerUnit1、 TServerContaine...