什么是Optional

Optional 是 Java8 提供的为了解决 null 安全问题的一个 API。善用 Optional 可以使我们代码中很多繁琐、丑陋的设计变得十分优雅
Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测

常用方法

  1. //of():为非null的值创建一个Optional
  2. Optional<String> optional = Optional.of("bam");
  3. // isPresent(): 如果值存在返回true,否则返回false
  4. optional.isPresent(); // true
  5. //get():如果Optional有值则将其返回,否则抛出NoSuchElementException
  6. optional.get(); // "bam"
  7. //orElse():如果有值则将其返回,否则返回指定的其它值
  8. optional.orElse("fallback"); // "bam"
  9. //ifPresent():如果Optional实例有值则为其调用consumer,否则不做处理
  10. optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"

of()和ofNullable区别:
ofNullable(可以传递一个空对象)
Of(不可以传递空对象)

  1. //ofNullable
  2. Optional<Object> o = Optional.ofNullable(null);
  3. System.out.println(o);//Optional.empty
  4. boolean s = o.equals(null);
  5. System.out.println(s);//false
  6. //of
  7. Optional<Object> o = Optional.of(null);
  8. System.out.println(o);
  9. boolean s = o.equals(null);
  10. System.out.println(s);
  11. //此时直接报空指针异常
  12. /*
  13. java.lang.NullPointerException
  14. at java.util.Objects.requireNonNull(Objects.java:203)
  15. at java.util.Optional.<init>(Optional.java:96)
  16. at java.util.Optional.of(Optional.java:108)
  17. at com.yun.Java8ApplicationTests.optionalTest(Java8ApplicationTests.java:95)
  18. */
  1. //当对象为空时
  2. People people = new People();
  3. System.out.println(people);//People(name=null, age=null)
  4. String result = Optional
  5. .ofNullable(people)//People(name=null, age=null)
  6. .map(p -> p.getName())
  7. .orElse("蒂法");
  8. System.out.println(result);//蒂法
  9. //当对象不为空时
  10. People people = new People("爱丽丝",22);
  11. System.out.println(people);//People(name=爱丽丝, age=22)
  12. String result = Optional
  13. .ofNullable(people)//People(name=爱丽丝, age=22)
  14. .map(p -> p.getName())
  15. .orElse("蒂法");
  16. System.out.println(result);//爱丽丝

因此,明确对象不为 null 的时候使用 of()
如果对象即可能是 null 也可能是非 null,就应该使用 ofNullable() 方法:

如何正确使用 Optional

使用 Optional把下面这样的代码进行改写

  1. public static String getName(User u) {
  2. if (u == null || u.name == null)
  3. return "Unknown";
  4. return u.name;
  5. }

不过,千万不要改写成这副样子。

  1. public static String getName(User u) {
  2. Optional<User> user = Optional.ofNullable(u);
  3. if (!user.isPresent())
  4. return "Unknown";
  5. return user.get().name;
  6. }

这样改写非但不简洁,而且其操作还是和第一段代码一样。无非就是用 isPresent 方法来替代 u==null。这样的改写并不是 Optional 正确的用法。

  1. public static String getName(User u) {
  2. return Optional.ofNullable(u)
  3. .map(user->user.name)
  4. .orElse("Unknown");
  5. }

这样才是正确使用 Optional 的姿势。那么按照这种思路,可以安心的进行链式调用,而不是一层层判断了。看一段代码:

  1. public static String getChampionName(Competition comp) throws IllegalArgumentException {
  2. if (comp != null) {
  3. CompResult result = comp.getResult();
  4. if (result != null) {
  5. User champion = result.getChampion();
  6. if (champion != null) {
  7. return champion.getName();
  8. }
  9. }
  10. }
  11. throw new IllegalArgumentException("The value of param comp isn't available.");
  12. }

由于种种原因(比如:比赛还没有产生冠军、方法的非正常调用、某个方法的实现里埋藏的大礼包等等),我们并不能开心的一路 comp.getResult().getChampion().getName() 到底。而其他语言比如 kotlin,就提供了在语法层面的操作符加持:comp?.getResult()?.getChampion()?.getName()。所以在 Java 里我们使用Optional

  1. public static String getChampionName(Competition comp) throws IllegalArgumentException {
  2. return Optional.ofNullable(comp)
  3. .map(Competition::getResult) // 相当于c -> c.getResult(),下同
  4. .map(CompResult::getChampion)
  5. .map(User::getName)
  6. .orElseThrow(()->new IllegalArgumentException("The value of param comp isn't available."));
  7. }

Optional 给了我们一个真正优雅的 Java 风格的方法来解决 null 安全问题。虽然没有直接提供一个操作符写起来短,但是代码看起来依然很爽很舒服
一些其他用法:
字符串为空则不打印

  1. string.ifPresent(System.out::println);

Optional 可以用来检验参数的合法性

  1. public void setName(String name) throws IllegalArgumentException {
  2. this.name = Optional.ofNullable(name)
  3. .filter(User::isNameValid)
  4. .orElseThrow(()->new IllegalArgumentException("Invalid username."));
  5. }

事实上,我们应该更进一步,减少 Optional.ofNullable 的使用。为什么呢?因为 Optional 是被设计成用来代替 null 以表示不确定性的,换句话说,只要一段代码可能产生 null,那它就可以返回 Optional。而我们选择用 Optional 代替 null 的原因,是 Optional 提供了一个把若干依赖前一步结果的处理结合在一起的途径。举个例子,在我们调用一个网站的登录接口的时候,大概会有以下的步骤:

  1. 发送 HTTP 请求,得到返回。
  2. (依赖:接口的返回)解析返回,如将 Json 文本形式的返回结果转化为对象形式。
  3. (依赖:解析的结果)判断结果是否成功。
  4. (依赖:若成功调用的结果)取得鉴权令牌。
  5. (依赖:获得的令牌)进行处理

其中,第 2-5 步的每一个步骤都依赖于前一个步骤,而前一个步骤传递过来的数据都具不确定性(有可能是 null)。所以,我们可以把它们接受的数据都设计成 Optional。第 1-4 步每一个步骤的结果也具备不确定性,所以我们也把它们的结果设计成 Optional。最后到了第 5 步,我们终于要对一切的结果进行处理了:如果成功获得令牌就存储,失败就提示用户。所以这一步,我们采用如 orElse 之类的方法来消除不确定性。于是我们最后的设计就可以是

  1. 结果 String(可能是 null) == 包装 ==> Optional<String>
  2. Optional<String> == 解析 ==> Optional<Json对象>
  3. Optional<Json对象> == Filter 判断成功 ==> Optional<Json对象>
  4. Optional<Json对象> == 取鉴权令牌 ==> Optional<AuthToken>
  5. Optional<AuthToken> 进行处理,消除 Optional

Optional 就像一个处理不确定性的管道,我们在一头丢进一个可能是 null 的东西(接口返回结果),经过层层处理,最后消除不确定性。Optional 在过程中保留了不确定性,从而把对 null 的处理移到了若干次操作的最后,以减少出现 NPE 错误的可能。于是,Optional 应用的建议也呼之欲出了:

  1. 适用于层级处理(依赖上一步操作)的场合。
  2. 产生对象的方法若可能返回 null,可以用 Optional 包装。
  3. 尽可能延后处理 null 的时机,在过程中使用 Optional 保留不确定性。
  4. 尽量避免使用 Optional 作为字段类型。

这种依赖上一步的操作也叫 Continuation。而 Optional 的这种接受并组合多个 Continuation 的设计风格就是 Continuation-passing style(CPS)

Optional在开发中的问题

orElse和orElseGet

orElse和orElseGet两者之间的区别细微, 但是却在某些场景下显的很重要.首先, 这是两个在java.util.Optional类中的方法,源码非常简单

  1. package java.util;
  2. public final class Optional<T> {
  3. ...
  4. public T orElse(T other) {
  5. return this.value != null ? this.value : other;
  6. }
  7. public T orElseGet(Supplier<? extends T> supplier) {
  8. return this.value != null ? this.value : supplier.get();
  9. }
  10. }

Supplier是一个函数式接口,只定义了一个返回值为T类型的get()方法,用于产生(提供)对象,可以使用lambda表达式定义实现orElse(T other) 不论容器是否为空,只要调用该方法, 则对象other一定存在orElseGet(Supplier<? extends T> supplier)只有当容器为空时,才调用supplier.get()方法产生对象
简而言之

  • 无论optional是否有值,orElse都会被执行,有值时,orElse执行的结果会被忽略
  • 只有optional为空时,orElseGet才会被执行

所以可见orElseGet()更优, 但代价就是需要传入一个Supplier<T>类型的参数,相对会麻烦一些
注意:
有个场景要注意。就是通过optional来操作数据库,如果查询到有数据,则update,没数据则执行insert
代码可能是这样

  1. Optional.ofNullable(selectEntity(id)).map(update(entity)).orElse(insert(entity))

这时,如果方法上再有事物注解@transactional,那么很有可能会引起Mysql事物锁等待超时 Lock wait timeout exceeded; try restarting transaction原因是:在同一事务内先后对同一条数据进行插入和更新操作。因为orElse是必会执行的所以orElse中,有具体方法操作时,一定要记得使用orElseGet

map和flatMap

map会将传入的Function函数的结果进行封装

  1. public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
  2. Objects.requireNonNull(mapper);
  3. if (!isPresent())
  4. return empty();
  5. else {
  6. return Optional.ofNullable(mapper.apply(value));//会使用Optional的ofNullable方法包装Function函数返回的值
  7. }
  8. }

flatMap会直接返回Function函数执行的结果,看源码:

  1. public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
  2. Objects.requireNonNull(mapper);
  3. if (!isPresent())
  4. return empty();
  5. else {
  6. return Objects.requireNonNull(mapper.apply(value));//直接返回Function执行的结果
  7. }
  8. }

可以得出map和flatMap的用法:
如果某对象实例的属性本身就为Optional包装过的类型,那么就要使用flatMap方法,此时返回的就是Optional类型的,所以不用再使用Optional进行包装。如果这时还选用map方法,那么返回值就是Optional>,编译时便会报错
对于返回值是其他类型,需要Optional进行包装,就需要使用map方法在其外面包装一层Optional对象。