前言
杨宗纬唱过一首歌,叫做《洋葱》,里面有一句是:“如果你愿意一层一层一层的剥开我的心。。。”,歌曲是非常的感人。
其实在咱们程序员日常开发中,也经常会遇见需要一层一层剥洋葱的情况,如下图:
比方说,现在咱们在后端需要请求某个接口,该接口返回格式如图,咱们需要得到items
数组里第一项的value
标签的值,可以发现,这就是个复杂对象一层一层 get 也就是一层一层剥洋葱的情况,每一次 get 都有可能为空,为了防止发生 NPE(空指针异常),咱们可能每一次 get 都得用 if 进行一个判空,像上图这种情况,可能得套四五个 if,代码非常臃肿。
这也引出了本文的主题,Optional
类,此类可以用来解决上述问题。
Optional 类简析
类的结构和构造方法
Optional
类的结构非常简单,如下:
此类有一个私有属性:value
,这是用来表示实际对象的,Optional
类其实算是个包装类,将要处理的对象置于value
,后面会说到;一个静态变量EMPTY
,其实是个饿汉模式的单例,表示空对象对应的 Optional
对象。
看完了它的属性,再看一下类的构造函数:
类的构造方法非常简单,接收一个value
参数赋给自己的私有属性。注意到构造函数是 private 的,因此后面肯定有方法调用它来实例化对象的。
常用方法
of 方法
说曹操,曹操到。果然出现了调用构造方法的方法,还是赋值value
属性,不过需要注意这里会调用Objects.requireNonNull(value)
,此方法还是会抛异常的,如下:
ofNullable 方法
和 of 方法不同的是,此方法会先判断要转换的对象是否为 null
,若是则返回(Optional<T>) EMPTY
,否则才会调用构造方法。可见,这个方法还是比较省心的,不用考虑太多 null
的问题,也不用担心抛 NPE。
判空方法
两个方法比较类似,就放在一块了,区别就是一个只是判断value
是否为 null
,另一个在判为非 null
之后会调用传入的 Consumer
接口对象对value
进行后续处理。
empty 方法
返回一个value
为空的Optional
对象。
map 方法
map 方法有两个,都是先判断 value
是否为 null
,若是,直接返回空的 Optional 类对象,否则,调用函数接口对象 mapper
的 apply 方法。
map 和 flatMap 方法的主要不同在于调用的函数接口对应的 apply 方法的返回值不同,map 方法返回的就是一个普通的类型,确定value
非空后,会调用Optional.ofNullable()
自动将mapper.apply(value)
的返回值包成一个 Optional
类的对象,比较方便省心;而 flatMap 方法需要你提供的函数接口的 apply 方法本身就返回 Optional
类的对象,而且也有抛出 NPE 的风险,比较费心。
当然,也不能通过所谓的省心费心来判断哪个方法更好,有些情况下,选择 flatMap 是更合适的。
filter 方法
代码也比较简单,先看看value
是否为 null
,若是,直接返回this
,不做处理;否则,调用predicate.test(value)
看看value
是否满足我们定义的某种规则,若不满足,返回empty();
,方法如其名,是一个用来过滤的方法。
orElse 方法
三个方法都是拿到value
值的,拿之前会做是否为 null
的判断,为 null
时,三个方法各自选择不同的处理办法,可以根据情景选择合适的方法。
小结
看完了 Optional
类的结构和主要方法,可以先做个小结了:Optional
类其实算是个包装类,我们可以把想处理的对象包在 Optional
类里面,Optional
类将该对象置于value
属性中,类本身提供了一些实用方法用来帮助对value
进行判空,过滤等处理。简化了我们的开发。
优雅的剥洋葱
说了这么多,让我们回到前言里那个问题吧,看看怎么用 Optional
类来优雅的剥洋葱。
我们先构造一个符合这种格式的 Json 对象:
假设现在我们请求某接口,得到的响应是上面的jsonData
这个对象(这里我就不写接口了,简单模拟一下)。我们想得到 items 数组第一项的 value 标签值,可以用下面的写法来 get 想要的值。
结果如下,符合预期
可以看出,这一连串的 get get get,如果不用 Optional
,那代码简直没法看了,这里代码里并没有判空操作,大家感兴趣可以对这个初始化的字符串进行修改,让某一次 get 为 null
,照样不会抛出 NPE,这里我就不演示了。当然,这里我 get 不到的处理是返回一个默认值 0,具体使用哪个方法处理还得看你自己的实际情况。
我们来分析一下,为啥不用写判空代码了呢?
首先是调用 ofNullable 方法,得到一个 Optional
对象,接着调用一连串的 map 方法,如果任何一次 get 的结果为 null
,通过上一节对 map 方法源码的解读,方法一进来会调用if (!isPresent())
来判断value
是否为 null
,如果是的话,直接return empty();
,那么下一次再调用 map 时,检查if (!isPresent())
仍然通过不了,还是return empty();
。所以只要某次 get 不到值,整个 map,filter,flatMap这一系列调用链都是返回一个value
为空的 Optional
对象,直到最后的 orElse 或者 orElseThrow 这种方法来处理。
Optional 的优缺点之我见
Optional
的优点不用说了,优雅,代码写出来链式调用,结构好看,不用大量的判空代码。不过个人感觉还是有一些缺点的:
- 比较占空间,毕竟把原来的对象又包了一层,所以如果只是简单判断一个对象是否为
null
,个人感觉直接 if 判断可能更好一点,而且语义还更加清晰 - 链式 map 的时候,无法得知具体哪一次 get 失败了