多此一举! 不要这样用 Java 8 Optional

Java 8 新加入了 Optional 类别,能省去繁琐的 null check 流程,丰富的 API 也让程式逻辑看起来更简洁、易读。但我却看到了不少错误的用法,反而让 Optional 显得多此一举。本篇探讨这些错误的用法,以及如何正确使用。

isPresent() and get()

假设有一个 studentService 可利用 id 查询学生资料,我们为了避免 return null 而后续可能导致 NPE,我们就必需在 studentService.readById 回传结果时先做 null check,因此传统写法会像这样:

public Student readById(String id) {    Student student = studentService.readById(id);    if (student != null) {        return student;    } else {        throw new NotFoundException(id);     }}

若改成 Optional 写法,并将 studentService.readById 改为回传 Optional<Student> 后,有些人可能会写成这样 :

public Student readById(String id) {    Optional<Student> student = studentService.readById(id);    if (student.isPresent()) {        return student.get();    } else {        throw new NotFoundException(id);     }}

很不幸的是,这应该是最常见的错误用法了,我们不难发现上面的 isPresent(), get() 和传统写法本质上是一样的,且增加了不必要的複杂度,可谓多此一举。

正确使用 Optional 方式如下:

public Student readById(String id) {    return studentService.readById(id).orElseThrow(() -> new NotFoundException(id));}

orElseThrow 会判断 Optional 的内容,若有值时则直接回传 Student;若没有,则抛出例外。不难看出 Optional 是与 Java 8 functional programming 写法相辅相成的,所以使用 Optional 时应搭配如 filter(), map(), orElseThrow() 等的 functional programming 风格的写法会比较适合。

一定有值,却依然使用 Optional

Optional 设计的意义就是用来表示 method 的回传值可能会是空的。但在某些一定会有回传值情况下,开发者却依然使用 Optional,这就造成了过度包装与多此一举。承上学生系统的例子,假设我们要查询全体学生中的第一名:

public Optional<Student> readTopScoreStudent() {    // ...}

正常来说,这个系统并不会没有学生资料(否则一切都是空谈),因此这个 method 肯定会有回传值,不需使用 Optional。通常需要透过 code review 才能发现类似的问题。

作为参数

有些人会将 Optional 作为参数,意图表示这个参数可能是非必要的:

public void setName(Optional<String> name) {    if (name.isPresent()) {        this.name = name.get();    } else {        this.name = "无名氏";    }}

但这是个不好的写法,因为这里的 Optional<String> 参数有三种可能的值:

有内容值的 OptionalOptional.empty()null

Optional 也有可能是个 null,当然有机会引发 NPE,让人更摸不着头绪,因此请不要使用 Optional 作为参数。此外,这样的写法会让 caller 很麻烦,因为他们必需将参数多包一层 Optional,变得不容易使用:

setName(Optional.of("Jason"));setName(Optional.empty());

因此,比较好的设计是透过 overloading,让参数有值或没有值的意图与结果更加明确:

public void setName() {    this.name = "无名氏";}public void setName(String name) {    this.name = name;}

另外,有此一说,Optional 若作为 Spring controller 的参数,则更能表达该参数是非必要的,例如:

@RequestMapping (value = "/submit/id/{id}", method = RequestMethod.GET, produces="text/xml")public String showLoginWindow(@PathVariable("id") String id,                              @RequestParam("username") Optional<String> username,                              @RequestParam("password") Optional<String> password) { ... }

在 Spring 4.1.1 后已经可以妥善处理这里的 Optional,它将不会是 null,再加上它是 controller,所以也不会难以被呼叫,因此有些人觉得这种作法比较好,这就见仁见智了。

作为 class field

public class Student {    private Optional<String> name;    // ...}

因为 Optional 是设计用来 method 的回传型态,因此它并没有实作序列化 Serializable 介面,在特定状况下需要物件序列化时将会出现问题。

Collection and Optional

因为 Optional 本身就是一个容器,如果内容又是另一个容器,例如回传 Optional<List<Student>>,这不仅比较複杂以外,在语意上还代表着三种可能的回传值:

一个有内容的 List一个空的 ListOptional.empty()

这样容易造成程式複杂与混淆,比较好的方式是:如果真的没有回传值,那就回传一个空的容器就好了:

Map and Optional

不要将 Optional 放入 Map,例如 Map<String, Optional<Student>>,原因和上述类似,在呼叫 map.get(key) 的回传值会是:

Optional (可能有 Student 与可能没有 Student)null

像这种错误用法都会提高不必要的複杂性。

References

本文转录自我的部落格 https://kaisheng714.github.io/articles/misuse-of-java-8-optional

更多你可能会感兴趣的文章

常见的 Interface 错误用法Java SimpleDateFormat 的错误用法

关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章