返回列表
共 6 条
发表主题
楼主

胡歌不会唱歌

Lv 1

41

主题

68

回复

3

粉丝
#
898 5
| 发表于 2019-10-22 14:08:31 显示全部楼层 |
跳转到指定楼层

[原创] 使用Optional的正确姿势(JAVA)

[复制链接]

作者:HW-YJ

0.前言

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

1.png

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

2.png

实质上是没有任何分别。

当我们切换到 Java 8 的 Optional 时,不能继承性的对待过往 null 时的那种思维,应该掌握好新的,正确的使用 Java 8 Optional 的姿势。

1. 引入Optional的动机

在Oracle做 Java语言工作的Brian Goetz在Stack Overflow回复Should Java 8 getters return optional type?中讲述了引入Optional的主要动机:

Our intention was 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. Optional提供了一个有限的机制让类库方法返回值清晰的表达有与没有值,避免很多时候 null 造成的错误。并非有了Optional 就要完全杜绝 NullPointerException。

在 Java 8 之前一个实践是方法返回集合或数组时,应返回空集合或数组表示没有元素;而对于返回对象,只能用 null 来表示不存在,现在可以用 Optional 来表示这个意义。

2. 使用Optional的4种坏味道

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

  1. 调用 isPresent() 方法时

  2. 调用 get() 方法时

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

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

2.1选择合适的方法

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 中我们真正可依赖的应该是除了 isPresent()get() 的其他方法:

3.png

2.2 切勿把Optional作为字段或方法参数

把 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 类型不可被序列化,用作字段类型会出问题的。

2.2.1 Optional 类型作为类/实例属性的问题

当我们选择 Optional 类型而非内部包装的类型后,应该是假定了该 Optional 类型不为 null,否则我们在使用 Optional 字段或方法参数时就变得复杂了,需要进行双重检查。

4.png

由于 middleName 的 setter 方法,我们可能造成 middleName 变为 null 值,所以在构建 fullName 时必须两重检查。

并且在调用 setMiddleName(...) 方法时也有些累赘了:

5.png

2.2.2 Optional 类型作为方法参数的问题

而如果字段类型非 Optional 类型,而传入的方法参数为 Optional 类型,要进行赋值的话

6.png

前面两段代码如果应用 Optional.ofNullable(...) 包裹 Optional 来替代 if(middleName != null) 就更复杂了。

对于本例直接用 String 类型的 middleName 作为字段或方法参数就行,null 值可以表达没有 middleName。如果不允许 null 值 middleName, 显式的进行入口参数检查而拒绝该输入(抛出异常)。

3. Optional的三种构造方式

  • Optional.empty():静态工厂方法,创建一个空的Optional对象

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

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

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

7.png

知道被包裹的值不可能为 null 时调用 ofNullable(value) ,相应的对于非 null 值的字面常量,会多了一次多余的 null 值检查。

当我们非常非常地明确将要传给 Optional.of(obj)obj 参数不可能为 null 时,比如它是一个刚 new 出来的对象(Optional.of(new User(...))),或者是一个非 null 常量时;

当想为 obj 断言不为 null 时,即我们想在万一 obj 为 null 立即报告 NullPointException 异常,立即修改,而不是隐藏空指针异常时,我们就应该果断的用 Optional.of(obj) 来构造 Optional 实例,而不让任何不可预计的 null 值有可乘之机隐身于 Optional 中。

4. 正确使用已有的 Optional 实例

假定我们有一个实例 Optional<User> user, 下面是几个普遍的原则:

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

8.png

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

9.png

4.3 存在才对它做点什么

10.png

4.4 好用的map方法

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

11.png

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

12.png

没有Optional,每一级的调用展开都要进行一次null判断:

13.png

isPresent() 处理 NullPointerException 并不优雅,用了orElseorElseGet等,特别是 map 方法才叫优雅。

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

5. 代码现状与优化建议

5.1. 滥用Optional

14.png


问题:

  • 在前文中,我们已经指出了Optional的用途:返回对象时用Optional表示null,但这显然不适用于容器。对于容器,直接返回空容器即可。

《华为Java语言通用编程规范-V4.5》 建议5.5:对于返回数组或者容器的方法,应返回长度为0的数组或者容器,代替返回null。

  • 调用处对Optional使用了isPresent()get()方法,本质和判空无区别。

整改后:

直接返回容器,并修改对应调用

15.png

5.2 丢失了Optional精髓的繁琐用法

16.png
问题:对Optional使用了isPresent()get()方法,本质和判空无区别。此处应使用orElseThrow方法。

整改后:

17.png

5.3 不安全的get

18.png

问题:

  • get方法没有判空保证

  • vmHostOptional命名很怪异

整改后:

19.png

5.4 不合适的构造方法

20.png

整改建议:
  • 使用of()方法构造Optional,如果configState为null会直接抛异常,不符合此处的逻辑。应该使用ofNullable()

  • configStateTypeconfigStateType.getConfigStates的判空采用传统方式,可以采用链式表达。

  • filter()内部逻辑建议抽取成一个小方法,提升可读性。

5.5 Optional作为方法参数


问题:

  • Optional作为方法参数

  • 同5.1,consume方法直接返回空容器即可

整改后:

23.png24.png


5.6 Optional作为类实例的属性


整改建议:

  • Optional作为类实例变量,并直接导致对应set方法使用Optional参数。Optional变量/参数会导致需要进行双重检查。此处恢复为普通对象即可。

  • 这是一个数据类,可以使用lombok简化代码。

6. 总结

  • 要理解 Optional 的设计用意,所以语意上应用它来表达有/无结果,不适于作为类字段与方法参数。

  • 倾向于方法返回单个对象,用 Optional 类型表示无结果以避免 null 值的二义性。

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


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

小闹心

Lv 1

34

主题

84

回复

0

粉丝
#
发表于 2019-10-22 15:28:36 | 显示全部楼层

很棒的帖子,代码也很清晰,学习了

以梦为码

Lv 1

22

主题

220

回复

1

粉丝
#
发表于 2019-10-22 15:34:44 | 显示全部楼层

好文好文

逾墙折枝

Lv 1

7

主题

63

回复

0

粉丝
#
发表于 2019-10-22 16:02:23 | 显示全部楼层

说实话,有点看不懂哈哈哈

听风就是雨

Lv 1

1

主题

35

回复

0

粉丝
#
发表于 2019-10-22 16:03:11 | 显示全部楼层

这文章,很厉害的样子

大老粗说天下

Lv 1

3

主题

27

回复

0

粉丝
#
发表于 2019-10-22 16:06:53 | 显示全部楼层

顶顶,不错啊

返回列表
共 6 条
发表主题
您需要登录后才可以回帖 登录 注册

快速回复 返回顶部 返回列表