返回 登录
0

了解、接受和利用Java中的Optional (类)

阅读505

本文转自微信号EAWorld。扫描下方二维码,关注成功后,回复“普元方法+”,将会获得热门课堂免费学习机会!本文转自微信号EAWorld。

1.概述

Java 8 最有趣的特性之一,就是引入了全新的 Optional 类。该类主要用来处理几乎每位程序员都碰到过的麻烦问题—— 空指针异常(NullPointerException)。

从本质上来说,该类属于包含可选值的封装类(wrapper class),因此它既可以包含对象也可以仅仅为空。

伴随着 Java函数式编程方式的异军突起,Optional 应运而生,除了可助该编程方式一臂之力外,Optional 的作用显然还远不止于此。

我们先来看一个简单的案例。在 Java 8 之前,凡涉及到访问对象方法或者对象属性的操作,无论数量多寡,都可能导致 空指针异常:

图片描述

假如我们想保证上面的小示例不出现异常,我们可能需要在访问它之前对每一个值进行显式检查:

图片描述

这么一来,会让代码显得累赘而难以维护。

为简化这一过程,我们将使用 Optional 类取代上述代码,从创建和验证一个实例开始,再到使用其提供的不同方法,最后将其和返回相同类型的其他方法进行组合,而最后这项组合功能正是 Optional 的真正强大之处。

2.创建 Optional 实例

为了实现重复迭代(reiterate),该类型对象既可以包含一个值,也可以为空。我们先用具有相同名称的方法来创建一个空 Optional:

图片描述

毫无疑问,如果您要访问 emptyOpt 变量的值,会导致 NoSuchElementException异常。

您可以用 of() 和 ofNullable(),来创建包含一个值的Optional 对象。两种方法的区别在于:如果你将 null 值作为参数传入 of() 方法,那么该方法会抛出一个 空指针异常。

图片描述
如你所见,空指针 异常的问题并没有得到彻底解决。因此,只有当对象不为 null 时, of()的方法才可行。

如果对象既可能为 null ,也可能为非 null ,就必须选择 ofNullable()。

图片描述

访问 Optional 对象的值

想要获取Optional实例内部的对象,方法之一是使用get()方法

图片描述

但和之前类似,这种方法在值为 null 时也会抛出异常。为避免出现异常,您可选择首先检验其中是否存在值。

图片描述

利用 ifPresent()也可以用来检查是否存在值。而且该方法还带有一个 Consumer 参数,在对象不为空时执行 λ 表达式:

图片描述

在此示例中,只有在用户对象非空时,才会执行assertion。

接下来,我们看看能够替换空值的各种方法。

返回默认值

Optional 类提供了一些 API,用于返回对象值或在对象为空时返回默认值。
其中的第一种方法是 orElse(),它的工作方式相当直接:如果存在值,则返回该值,如果不存在值,则返回它收到的参数:

图片描述

此处,user 对象为空,所以 user2 作为默认替代值返回。

如果对象的初始值不为空,则默认值会被忽略:

图片描述

第二种同类 API 是 orElseGet() ——其工作方式略有不同。在本例中,如果存在值,则方法回返该值,如果不存在,则其执行 Supplier 函数接口(作为其收到的一个参数),并返回执行结果:

图片描述

orElse() 和 orElseGet() 之间的区别

乍一看,两种方法似乎效果相同。但实际还是有差别。我们可以通过创建几个例子,来看看二者在功能表现上的相似处和不同点。

首先,我们来看对象为空时,二者的表现:

图片描述

在上面的代码中,两种方法都调用了createNewUser() 方法,后者会记录消息日志并返回 User 对象。

代码输出如下:

图片描述

可见,当对象为空时,二者在表现上并无差别,都是代之以返回默认值。

接下来,我们举一个 Optional 不为空时的相似例子:

图片描述

这次的输出如下:

图片描述
此处,两个 Optional 对象都包含有一个非空值,而两种方法都会将其作为返回值。但是,orElse() 方法仍然会创建默认的 User 对象。相反,orElseGet() 方法将不再创建 User 对象。

当操作中包含大量密集调用时,比如 web 服务调用或者数据库查询,这种差别就会对代码执行产生重大影响。

返回异常

除了 orElse() 和 orElseGet() 方法,Optional还定义了 ElseThrow() API,其作用是在对象为空时,直接抛出一个异常,而不是返回一个替代值。

图片描述

此处,如果 user 值为空,则会抛出 非法参数异常。

这让我们可以从更多灵活的语义中挑选所要抛出的异常,而不是千篇一律的 空指针异常。

既然我们已对 Optional 本身的使用有了一定了解,那就让我们再来看看用于转换和过滤 Optional 值的其他方法。

3.对值进行转换

Optional 值可通过多种方法进行转换;我们就从 map() 和 flatMap() 说起。

首先,让我们看个使用 map() API 的例子:

图片描述

Map() 将 Function 参数作为值,然后返回 Optional 中经过封装的结果。这将使我们可以在后续附加一些操作,比如此处的 orElse() 。

相比之下,flatMap() 也是将 Function 参数作为 Optional 值,但它后面是直接返回结果。

为了查看实际效果,我们添加一个方法,可向 User 类返回 Optional:

图片描述

因为 getter 方法返回一个 Optional 字符串值,在请求Optional User 对象时,您可将其作为 flatMap() 的参数。返回值为非封装字符串值:

图片描述

4.对值进行过滤

除了对值进行转换的功能,Optional 类还提供了根据条件对值进行“过滤”的功能。

filter() 方法将 predicate 作为参数,当测试评估为真时,返回实际值。否则,当测试为假时,返回值则为空 Optional。

我们来看一个例子——基于非常基本的电子邮件验证,接受或者拒绝 User:

图片描述
作为通过过滤测试的结果,Result 对象将包含一个非 null 值。

5.对 Optional 类的方法进行链接

Optional 还具有更多强大的应用,鉴于绝大多数 Optional 方法会返回相同类型的对象,您可以将它们的不同组合链接起来。

我们把示例代码重新写一下。

首先,我们重构这些类,这样 getter 方法将返回 Optional 引用

图片描述
图片描述

上述结构可用嵌套集合来直观地表示:

图片描述

现在删除对 null 进行检查的代码,并以 Optional 方法来取代:

图片描述
上面的代码可通过方法引用(method references)做进一步精简:

图片描述

从现在的结果看,代码比先前冗长的条件驱动(conditional-driven)版本要简洁许多。

6.Java 9 新增特性

在 Java 8 引入Optional特性的基础上,Java 9 又为 Optional 类增加了三种方法:or()、ifPresentOrElse() 和 stream()。

在某种意义上,or() 方法同 orElse() 和 orElseGet() 类似,都是在对象为空时提供替换功能。在本例中,返回值为另一个由 Supplier 参数生成的 Optional 对象。

如果对象包含一个值,则λ表达式不会执行:
图片描述

在上述示例中,如果 user 变量为空,则将返回包含一个带有电子邮件“default”的 User 对象的 Optional 。

ifPresentOrElse() 方法带有两个参数:Consumer 和 Runnable。如果对象包含一个值,则会执行 Consumer 动作;否则,会执行 Runnable 动作。

如果您希望使用某个现有值执行一个动作,或者仅仅想跟踪某个值是否已作定义,则该方法非常有用:

图片描述

最后,您可从新 stream() 方法的扩展 Stream API 得到益处,具体做法是将实例转换为一个 Stream 对象。如果 Optional 不存在值,则 Stream 为空,如果 Optional 包含一个非 null 值,则 Stream 会包含单个值。

我们举个将 Optional 作为 Stream 处理的例子:

图片描述

在此处使用 Stream ,使得应用 filter()、map() 和 collect() 等 Stream 接口方法 来获取 List 成为可能。

7.应该如何使用 Optional

在使用 Optional 时,我们需要考虑几个问题,来决定什么时候用以及如何用。

第一个要点,Optional 并不能序列化(Serializable )。因此,它不可以在类中当作一个字段(field)来使用。

如果您需要序列化一个包含 Optional 值的对象,Jackson library(https://stackify.com/java-xml-jackson/)可支持将 Optionals当作普通对象来对待。这意味着,Jackson 会将空对象作为 null,它还会将有值对象当作一个包含该值的字段。这个功能可在 jackson-modules-java8 (https://github.com/FasterXML/jackson-modules-java8) 项目中找到。

另一种不太适合使用该类型的情况,是将该类型作为方法或者构造函数的参数。这将导致不必要的代码复杂化。

图片描述

相反,使用方法重载(method overloading)来处理非强制性参数要方便得多。

Optional的主要用途是作为一种返回类型。在获得该类型的一个实例后,如果存在值,您可以提取该值,如果不存在值,则您可以获得一个替换值。

Optional类对我们最有帮助的一个用例,是其同 stream 或者其他方法的组合使用,这些方法会返回一个可构建流畅 API 的Optional 值。

我们举个使用 Stream findFirst() 方法并返回 Optional 对象的例子:

图片描述

8.总结

对于 Java 语言来说,Optional 是一项非常有用的新增特性。尽管无法彻底消除 空指针异常,但 Optional 可以最大限度减少代码执行过程中出现的此类异常。

同时,该类经过精心设计,对于 Java 8 加入的新函数式支持(functional support)而言,它自然而然地成为非同一般的新增特性。

总之,该类简单而不失强大,相比之前的同类功能,用其编写代码既简单易读又不易出错。

原文链接:https://stackify.com/optional-java/

关于作者:
Eugen是一名软件工程师,对Spring、REST API、安全和教育拥有极大热情。同时,他还是Baeldung(推特账号@baeldung)的创始人。

关于EAWorld
微服务,DevOps,元数据,企业架构原创技术分享,EAii(Enterprise Architecture Innovation Institute)企业架构创新研究院旗下官方微信公众号。
扫描下方二维码,关注成功后,回复“普元方法+”,将会获得热门课堂免费学习机会!
微信号:EAWorld,长按二维码关注。

图片描述

评论