`

使用 Java8 Optional 的正确姿势(转)

    博客分类:
  • java
 
阅读更多

我们知道 Java 8 增加了一些很有用的 API, 其中一个就是 Optional. 如果对它不稍假探索, 只是轻描淡写的认为它可以优雅的解决 NullPointException 的问题, 于是代码就开始这么写了

Optional<User> user = ……
if (user.isPresent()) {
return user.getOrders();
} else {
return Collections.emptyList();
}

那么不得不说我们的思维仍然是在原地踏步, 只是本能的认为它不过是 User 实例的包装, 这与我们之前写成

User user = …..
if (user != null) {
return user.getOrders();
} else {
return Collections.emptyList();
}

实质上是没有任何分别. 这就是我们将要讲到的使用好 Java 8 Optional 类型的正确姿势.

在里约奥运之时, 新闻一再提起五星红旗有问题, 可是我怎么看都看不出来有什么问题, 后来才道是小星星膜拜中央的姿势不对. 因此我们千万也别对自己习以为常的事情觉得理所当然, 丝毫不会觉得有何不妥, 换句话说也就是当我们切换到 Java 8 的 Optional 时, 不能继承性的对待过往 null 时的那种思维, 应该掌握好新的, 正确的使用 Java 8 Optional 的正确姿势.

直白的讲, 当我们还在以如下几种方式使用 Optional 时, 就得开始检视自己了

  1. 调用 isPresent() 方法时

  2. 调用 get() 方法时

  3. Optional 类型作为类/实例属性时

  4. Optional 类型作为方法参数时

isPresent() 与 obj != null 无任何分别, 我们的生活依然在步步惊心. 而没有 isPresent() 作铺垫的 get() 调用在 IntelliJ IDEA 中会收到告警

Reports calls to java.util.Optional.get() without first checking with a isPresent() call if a value is available. If the Optional does not contain a value, get() will throw an exception. (调用 Optional.get() 前不事先用 isPresent() 检查值是否可用. 假如 Optional 不包含一个值, get() 将会抛出一个异常)

把 Optional 类型用作属性或是方法参数在 IntelliJ IDEA 中更是强力不推荐的

Reports any uses of java.util.Optional<T>, java.util.OptionalDouble, java.util.OptionalInt, java.util.OptionalLong or com.google.common.base.Optional as the type for a field or a parameter. Optional was designed to provide a limited mechanism for library method return types where there needed to be a clear way to represent “no result”. Using a field with type java.util.Optional is also problematic if the class needs to be Serializable, which java.util.Optional is not. (使用任何像 Optional 的类型作为字段或方法参数都是不可取的. Optional 只设计为类库方法的, 可明确表示可能无值情况下的返回类型. Optional 类型不可被序列化, 用作字段类型会出问题的)

所以 Optional 中我们真正可依赖的应该是除了 isPresent() 和 get() 的其他方法:

  1. public<U> Optional<U> map(Function<? super T, ? extends U> mapper)

  2. public T orElse(T other)

  3. public T orElseGet(Supplier<? extends T> other)

  4. public void ifPresent(Consumer<? super T> consumer)

  5. public Optional<T> filter(Predicate<? super T> predicate)

  6. public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)

  7. public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X

我略有自信的按照它们大概使用频度对上面的方法排了一下序.

先又不得不提一下 Optional 的三种构造方式: Optional.of(obj),  Optional.ofNullable(obj) 和明确的 Optional.empty()

Optional.of(obj): 它要求传入的 obj 不能是 null 值的, 否则还没开始进入角色就倒在了 NullPointerException 异常上了.

Optional.ofNullable(obj): 它以一种智能的, 宽容的方式来构造一个 Optional 实例. 来者不拒, 传 null 进到就得到 Optional.empty(), 非 null 就调用 Optional.of(obj).

那是不是我们只要用 Optional.ofNullable(obj) 一劳永逸, 以不变应二变的方式来构造 Optional 实例就行了呢? 那也未必, 否则 Optional.of(obj) 何必如此暴露呢, 私有则可?

我本人的观点是:  1. 当我们非常非常的明确将要传给 Optional.of(obj) 的 obj 参数不可能为 null 时, 比如它是一个刚 new 出来的对象(Optional.of(new User(...))), 或者是一个非 null 常量时;  2. 当想为 obj 断言不为 null 时, 即我们想在万一 obj 为 null 立即报告 NullPointException 异常, 立即修改, 而不是隐藏空指针异常时, 我们就应该果断的用 Optional.of(obj) 来构造 Optional 实例, 而不让任何不可预计的 null 值有可乘之机隐身于 Optional 中.

现在才开始怎么去使用一个已有的 Optional 实例, 假定我们有一个实例 Optional<User> user, 下面是几个普遍的, 应避免 if(user.isPresent()) { ... } else { ... } 几中应用方式.

存在即返回, 无则提供默认值

1
2
return user.orElse(null);  //而不是 return user.isPresent() ? user.get() : null;
return user.orElse(UNKNOWN_USER);

存在即返回, 无则由函数来产生

1
return user.orElseGet(() -> fetchAUserFromDatabase()); //而不要 return user.isPresent() ? user: fetchAUserFromDatabase();

存在才对它做点什么

1
2
3
4
5
6
user.ifPresent(System.out::println);
 
//而不要下边那样
if (user.isPresent()) {
  System.out.println(user.get());
}

map 函数隆重登场

user.isPresent() 为真, 获得它关联的 orders, 为假则返回一个空集合时, 我们用上面的 orElse, orElseGet 方法都乏力时, 那原本就是 map 函数的责任, 我们可以这样一行

1
2
3
4
5
6
7
8
return user.map(u -> u.getOrders()).orElse(Collections.emptyList())
 
//上面避免了我们类似 Java 8 之前的做法
if(user.isPresent()) {
  return user.get().getOrders();
else {
  return Collections.emptyList();
}

map  是可能无限级联的, 比如再深一层, 获得用户名的大写形式

1
2
3
return user.map(u -> u.getUsername())
           .map(name -> name.toUpperCase())
           .orElse(null);

这要搁在以前, 每一级调用的展开都需要放一个 null 值的判断

1
2
3
4
5
6
7
8
9
10
11
User user = .....
if(user != null) {
  String name = user.getUsername();
  if(name != null) {
    return name.toUpperCase();
  else {
    return null;
  }
else {
  return null;
}

针对这方面 Groovy 提供了一种安全的属性/方法访问操作符 ?.

1
user?.getUsername()?.toUpperCase();

Swift 也有类似的语法, 只作用在  Optional 的类型上.

用了 isPresent() 处理 NullPointerException 不叫优雅, 有了  orElse, orElseGet 等, 特别是 map 方法才叫优雅.

其他几个, filter() 把不符合条件的值变为 empty(),  flatMap() 总是与 map() 方法成对的,  orElseThrow() 在有值时直接返回, 无值时抛出想要的异常.

一句话小结: 使用 Optional 时尽量不直接调用 Optional.get() 方法, Optional.isPresent() 更应该被视为一个私有方法, 应依赖于其他像 Optional.orElse(), Optional.orElseGet(), Optional.map() 等这样的方法.

最后, 最好的理解 Java 8 Optional 的方法莫过于看它的源代码 java.util.Optional, 阅读了源代码才能真真正正的让你解释起来最有底气, Optional 的方法中基本都是内部调用  isPresent() 判断, 真时处理值, 假时什么也不做.

本文链接:http://unmi.cc/proper-ways-of-using-java8-optional/, 来自 隔叶黄莺 Unmi Blog

分享到:
评论

相关推荐

    使用Java8 Optional的正确姿势

    我们知道 Java 8 增加了一些很有用的 API, 其中一个是 Optional. 如果对它不稍假探索, 只是轻描淡写的认为它可以优雅的解决 NullPointException 的问题, 于是代码开始这么写了  Optional&lt;User&gt; user = ……  ...

    Java8Optional机制的正确使用方式共6页.pdf

    本篇文章将深入探讨Java 8 Optional的正确使用方式。 1. **Optional 的基本用法** - `Optional` 是一个容器对象,可能包含或不包含非null值。如果值存在则`isPresent()`返回`true`,调用`get()`会返回该对象。 - ...

    Java8Optional机制的正确使用方式共6页.pdf.zip

    Java 8 Optional 是一个强大的工具,它被引入来...了解并正确使用 Java 8 的 Optional 机制,能够显著提升代码的健壮性和可读性,减少潜在的运行时错误。但同时,也需要注意其使用场景和避免误用,才能真正发挥其优势。

    使用Java8中Optional机制的正确姿势

    以下是一些使用 `Optional` 的正确姿势: 1. **避免直接调用 `get()`**: 直接调用 `Optional.get()` 是不推荐的,因为如果 `Optional` 没有值(即 `isEmpty()` 或 `isPresent()` 返回 `false`),`get()` 会抛出 ...

    利用Java8 Optional如何避免空指针异常详解

    Java 8 中的 Optional 类是为了解决传统编程中常见的空指针异常(NullPointerException)而引入的一个工具类。Optional 作为一个容器...在编写 Java 8 及更高版本的代码时,适当地使用 Optional 能够显著提升代码质量。

    Java+8实战_Java8_java8_

    5. **日期和时间API**:Java 8用全新的`java.time`包取代了过时的`java.util.Date`和`java.util.Calendar`,提供了更直观、更易于使用的日期和时间API,如`LocalDate`、`LocalTime`和`LocalDateTime`。 6. **默认...

    Java8中文文档

    这份"Java8中文文档"是针对Java 8编程语言的中文参考资料,由百度翻译提供,旨在帮助中国开发者更好地理解和使用Java 8的新特性。尽管中文翻译可能在准确性和完整性上有所妥协,但作为辅助学习材料,它仍然是非常有...

    java8中文api

    总的来说,"java8中文api"这个文档涵盖了Java 8的所有新特性和重要API,包括Lambda表达式、Stream API、函数式接口、日期和时间API、Optional类以及并发改进等。无论你是初学者还是有经验的开发者,这个文档都将是你...

    Java8 jdk安装包

    8. **Optional类**:为了解决null安全问题,Java8引入了`Optional`类。它是一个容器对象,可以保存一个值,也可能没有值。这有助于避免空指针异常。 以上就是Java8安装和主要特性的详细说明。理解并掌握这些内容,...

    java8中文文档API

    Java 8是Java编程语言的一个重要版本,引入了许多新特性,极大地提升了开发效率和...以上这些特性都在"java8中文文档API"中有所详细介绍,通过这个文档,开发者可以深入理解Java 8的新特性和使用方法,提升开发效率。

    Java如何使用Optional与Stream取代if判空逻辑(JDK8以上)

    Java 8引入了两个强大的工具,Optional 和 Stream,它们极大地改善了处理可能为空的对象的方式,减少了if语句的使用,使代码更加简洁、易读。本文将深入探讨如何使用这两个特性来取代传统的if判空逻辑。 首先,让...

    java8 官方文档.

    7. **Optional类**:为了解决空指针异常问题,Java 8引入了`Optional&lt;T&gt;`类。它是一个容器对象,可能包含或不包含非null值。如果值存在则`isPresent()`返回true,调用`get()`会返回该对象。这有助于编写更清晰的代码...

    java8 中文文档

    这个中文文档是为初学者和有经验的开发者提供的一份实用参考资料,它详细介绍了 Java 8 的各种功能和使用方法。下面我们将深入探讨一些 Java 8 的核心知识点。 1. **函数式编程**: - **Lambda 表达式**:Java 8 ...

    Java8API文档(官方离线版)

    首先,Java 8引入了lambda表达式,这是一种简洁的匿名函数形式,可以作为参数传递或在任何期望函数的地方使用。Lambda表达式的语法是`(parameters) -&gt; expression`,例如`(int x, int y) -&gt; x + y`。这使得处理集合...

    java8源代码内容

    这个压缩包文件“jdk1.8”包含了Java 8的源代码,这对于开发者来说是一个宝贵的资源,可以深入理解其内部工作原理,帮助在实际项目中更高效地使用Java 8的功能。 首先,Java 8的最重要的新特性之一是Lambda表达式。...

    java8 API 中文版

    Java 8 API是Java开发的重要参考资料,它包含了Java 8版本的所有类库、接口和方法的详细说明。这个中文版的API文档对于中国开发者来说,无疑提供了极大的便利,因为中文语言的理解更为直观,有助于提高开发效率。...

    java 8 安装包

    6. **Optional类**:为了解决null对象引用带来的问题,Java 8引入了Optional类,它是一个可以为null的容器对象,通过Optional可以更好地表示可能缺失的值,避免空指针异常。 7. ** Nashorn JavaScript引擎**:Java ...

    Java8 64位免安装版

    7. **Optional类**:为了解决null值带来的问题,Java 8引入了`Optional`类,它是一个可以为null的容器对象,有助于减少空指针异常的发生。 配置Java 8 64位免安装版的环境变量主要涉及以下几步: 1. **设置JAVA_...

    Java 判空 Optional 详解

    Java 判空 Optional 详解

Global site tag (gtag.js) - Google Analytics