zoukankan      html  css  js  c++  java
  • 浅析Java8新特性-Optional方法介绍(Optional.ofNullable-构造方法、map-逐层安全地拆解value、filter-过滤值、orElse/orElseThrow-最终返回、stream-转为流)及常用实践(仅作为方法返回值、清晰地表达返回值中没有结果的可能性、勿滥用Optional)、Optional的设计思想实现

      Optional 是 Java 8 引进的一个新特性,通常用于缓解常见的空指针异常问题。Brian Goetz (Java语言设计架构师)对Optional设计意图的原话如下:

    Optional is intended to provide a limited mechanism for library method return types where there needed to be a clear way to represent “no result," and using null for such was overwhelmingly likely to cause errors.

      这句话突出了三个点:

    1、Optional 是用来作为方法返回值

    2、Optional 是为了清晰地表达返回值中没有结果的可能性

    3、且如果直接返回 null 很可能导致调用端产生错误(尤其是NullPointerException)

      Optional 的机制类似于 Java 的受检异常,强迫API调用者面对没有返回值的现实。参透 Optional 的设计意图才能学会正确得使用它。下面介绍一下Optional方法以及围绕这三个点阐述 Optional的最佳实践。

    一、Optional的相关方法介绍

    1、JDK 提供三个静态方法来构造一个Optional:我们主要记住这个就行了 - Optional.ofNullable(T value)

    (1)Optional.of(T value),该方法通过一个非 null 的 value 来构造一个 Optional,返回的 Optional 包含了 value 这个值。对于该方法,传入的参数一定不能为 null,否则便会抛出 NullPointerException。

    (2)Optional.ofNullable(T value),该方法和 of 方法的区别在于,传入的参数可以为 null。该方法会判断传入的参数是否为 null,如果为 null 的话,返回的就是 Optional.empty()。

    (3)Optional.empty(),该方法用来构造一个空的 Optional,即该 Optional 中不包含值,其实底层实现还是 如果 Optional 中的 value 为 null 则该 Optional 为不包含值的状态,然后在 API 层面将 Optional 表现的不能包含 null 值,使得 Optional 只存在 包含值 和 不包含值 两种状态。

    2、 ifPresent ——  如果 Optional 中有值,则对该值调用 consumer.accept,否则什么也不做。

    3、orElse  ——  如果 Optional 中有值则将其返回,否则返回 orElse 方法传入的参数。

    4、orElseGet   ——  与 orElse 方法的区别在于,orElseGet 方法传入的参数为一个 Supplier 接口的实现 —— 当 Optional 中有值的时候,返回值;当 Optional 中没有值的时候,返回从该 Supplier 获得的值。

    User user = Optional.ofNullable(getUserById(id))
            .orElse(new User(0, "Unknown"));
    
    User user = Optional.ofNullable(getUserById(id))
            .orElseGet(() -> new User(0, "Unknown"));

    5、orElseThrow  ——  与 orElse 方法的区别在于,orElseThrow 方法当 Optional 中有值的时候,返回值;没有值的时候会抛出异常,抛出的异常由传入的 exceptionSupplier 提供。

    User user = Optional.ofNullable(getUserById(id))
            .orElseThrow(() -> new EntityNotFoundException("id 为 " + id + " 的用户没有找到"));

      举一个 orElseThrow 的用途:在 SpringMVC 的控制器中,我们可以配置统一处理各种异常。查询某个实体时,如果数据库中有对应的记录便返回该记录,否则就可以抛出 EntityNotFoundException ,处理 EntityNotFoundException 的方法中我们就给客户端返回Http 状态码 404 和异常对应的信息 —— orElseThrow 完美的适用于这种场景。

    @RequestMapping("/{id}")
    public User getUser(@PathVariable Integer id) {
        Optional<User> user = userService.getUserById(id);
        return user.orElseThrow(() -> new EntityNotFoundException("id 为 " + id + " 的用户不存在"));
    }
    
    @ExceptionHandler(EntityNotFoundException.class)
    public ResponseEntity<String> handleException(EntityNotFoundException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }

    6、map  ——  如果当前 Optional 为 Optional.empty,则依旧返回 Optional.empty;否则返回一个新的 Optional,该 Optional 包含的是:函数 mapper 在以 value 作为输入时的输出值。而且我们可以多次使用map操作:

    Optional<String> username = Optional
            .ofNullable(getUserById(id))
            .map(user -> user.getUsername())
            .map(name -> name.toLowerCase())
            .map(name -> name.replace('_', ' '));

    7、filter 方法接受一个 Predicate 来对 Optional 中包含的值进行过滤,如果包含的值满足条件,那么还是返回这个 Optional;否则返回 Optional.empty。

    Optional<String> username = Optional.ofNullable(getUserById(id))
            .filter(user -> user.getId() < 10)
            .map(user -> user.getUsername());

    8、or 方法的作用是,如果一个 Optional 包含值,则返回自己;否则返回由参数 supplier 获得的 Optional

    9、ifPresentOrElse 方法的用途是,如果一个 Optional 包含值,则对其包含的值调用函数 action,即 action.accept(value),这与 ifPresent 一致;与 ifPresent 方法的区别在于,ifPresentOrElse 还有第二个参数 emptyAction —— 如果 Optional 不包含值,那么 ifPresentOrElse 便会调用 emptyAction,即 emptyAction.run()

    10、stream 方法的作用就是将 Optional 转为一个 Stream,如果该 Optional 中包含值,那么就返回包含这个值的 Stream;否则返回一个空的 Stream(Stream.empty())。

    二、Optional 是用来作为方法返回值的

    1、不要滥用 Optional API

      有的同学知道了一些Optional的API后就觉得找到了一把锤子,看到什么都像钉子,于是写出了以下这种代码

    String finalStatus = Optional.ofNullable(status).orElse("PENDING")
    // 这种写法不仅降低了代码可读性还无谓得创建了一个Optional对象(浪费性能)
    // 以下是同等功能但更简洁更可读的实现
    String finalStatus = status == null ? "PENDING" : status;

    2、不要使用Optional作为Java Bean实例域的类型,因为 Optional 没有实现 Serializable 接口(不可序列化)

    3、不要使用 Optional 作为类构造器参数

    4、不要使用 Optional 作为Java Bean Setter方法的参数

      原因除了上面第二点提到的 Optional 是不可序列化的,还有降低了可读性。

      既然 setter是用于给Java Bean 属性赋值的, 为什么还无法确定里面的值是不是空 ? 如果为空,为何不直接赋值 null (或者对应的空值) ?

      但相反的是,对于可能是空值 Java Bean 属性的 Getter 方法返回值使用 Optional 类型是很好的实践。

    @Entity
    public class Customer implements Serializable {private String postcode; // optional field, thus may be null
        public Optional<String> getPostcode() {
          return Optional.ofNullable(postcode);
        }
        public void setPostcode(String postcode) {
           this.postcode = postcode;
        }
        ...
    }

      由于getter返回的是Optional,外部调用时就意识到里面可能是空结果,需要进行判断。注意:对值可能为 null 的实例域的 getter 才需要使用 Optional。

    5、不要使用Optional作为方法参数的类型

      首先,当参数类型为Optional时,所有API调用者都需要给参数先包一层Optional(额外创建一个Optional实例)浪费性能 —— 一个Optional对象的大小是简单引用的4倍。其次,当方法有多个Optional参数时,方法签名会变得更长,可读性更差。

    三、Optional 是为了清晰地表达返回值中没有结果的可能性

    1、不要给Optional变量赋值 null,而应该用 Optional.empty() 表达空值

    2、确保Optional内有值才能调用 get() 方法

      如果不检查Optional是否为空值就直接调用get() 方法,就让 Optional 失去了意义 —— Optional 是为了清晰地表达返回值中没有结果的可能性,强迫API调用者面对没有返回值的现实并做检查。

      目前Java 8编译器并不会对这种情况报错,但是 IDE 已经可以识别并警告

    // 所以避免
    Optional<Cart> cart = ... ; // this is prone to be empty
    ...
    // if "cart"is empty then this code will throw a java.util.NoSuchElementException
    Cart myCart = cart.get();
    
    // 而应该
    if (cart.isPresent()) {
        Cart myCart = cart.get();
        ... // do something with "myCart"
    } else {
        ... // do something that doesn't call cart.get()
    }

    3、尽量使用 Optional 提供的快捷API 避免手写 if-else 语句

      在一些场景下, Optional.orElse() Optional.orElseGet() Optional.ifPresent() 可以避免手写 if-else 语句,使代码更简洁。

    public static final String USER_STATUS = "UNKNOWN";
    ...
    public String findUserStatus(long id) {
        Optional<String> status = ... ; // prone to return an empty Optional
        return status.orElse(USER_STATUS);
    }
    
    public String computeStatus() {
        ... // some code used to compute status
    }
    public String findUserStatus(long id) {
        Optional<String> status = ... ; // prone to return an empty Optional
        // computeStatus() is called only if "status" is empty
        return status.orElseGet(this::computeStatus);
    }
    
    Optional<String> status ... ;
    status.ifPresent(System.out::println);

    4、使用 equals 而不是 == 来比较 Optional 的值,Optional 的 equals 方法已经实现了内部值比较

      总结:

    (1)Optional 尽量只用来作为方法返回值类型

    (2)调用了返回值为Optional的方法后,一定要做空值检查

    (3)不要过度使用 Optional 避免降低代码可读性和性能

    (4)查阅并适当使用 Optional API

    四、常用 API 梳理

      这里梳理一下Optional的API,标出几个重点且适合我们使用的方法:

      最重要的就是学会3个方法:

    1、如何包装value:Optional.ofNullable()

    2、逐层安全地拆解value:map()

    3、最终返回:orElse()

      这三个搭配就是最佳实践。其他可能会用的方法就是orElseGet()、orElsethrow(),其他的不常用或者说不好用。

    五、Optional的设计思想实现

      详见这篇文章,写的挺清晰的:https://zhuanlan.zhihu.com/p/338128121

  • 相关阅读:
    做一名让开发佩服的测试(浅析)
    软件测试行业—职业发展之路(浅谈)
    移动端APP测试总结
    Http协议!(转)
    移动App书写Test Case时需要考虑的检查点
    黑盒测试方法——边界值分析(转)
    黑盒测试方法——等价类划分(转)
    在线网络实用工具
    Android Monkey压力测试(转)
    软件工程(浅谈)
  • 原文地址:https://www.cnblogs.com/goloving/p/15313045.html
Copyright © 2011-2022 走看看