请不要这样用Java 8 Optional"/>
多此一举!请不要这样用Java 8 Optional
我们都知道程序中尽量不要返回null,而Java 8 新加入了Optional 类别可避免NullPointerException 问题与繁琐的null check,可以让程序逻辑看起来更简洁、易读,也能清楚表达method 可能没有结果值。但我却看到了不少错误的用法,反而让Optional 显得多此一举。本篇探讨这些错误的用法,以及如何正确使用。
isPresent() 和 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 后,有些人可能会写成这样:
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 stream 写法相辅相成的,所以使用 Optional 时搭配如 filter(), map(), orElseThrow() 等的 stream 风格的写法会比较适合
一定有值,却依然使用 Optional
Optional 设计的意义就是用来表示方法 的返回值可能会是空的。但在某些一定会返回值得情況下,开发者却依然使用 Optional,这就造成了过度包装。承上学生系统的例子,假设我们要查詢全体学生中的第一名:
public Optional<Student> readTopScoreStudent() {// ...
}
正常来说,这个系统并不会沒有学生资料(否则一切都是空谈),因此这个 方法 肯定会有返回值,不需使用 Optional。通常需要通过 code review 才能发现类似的问题
作为参数
有些人会将 Optional 作为参数,意图表示这个参数可能是非必要的:
public void setName(Optional<String> name) {if (name.isPresent()) {this.name = name.get();} else {this.name = "无名氏";}
}
但这是个不好的写法,因为这里的 Optional 参数有3种可能的值:
- 有內容值的 Optional
- 可选的.empty()
- 无效的
Optional 也有可能是个 null,当然有机会引发 NPE,让人更摸不着头绪,因此请不要使用 Optional 作为差不是参数。此外,这样的写法会让 方法的调用者 很痛苦,因为他们必需将参数多包装一层 Optional,变得更加不容易使用
setName(Optional.of("Jason"));
setName(Optional.empty());
因此,比较好的设计是通过方法重载,让参数有值或沒有值结果果更加明确:
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 是设计用来方法的返回值,因此它并没有实现序列化 Serializable 接口,在特定状况下(如网路传输)需要序列化时将会出现问题
Collection and Optional
因为Optional 本身就是一個容器,如果內容又是另一個容器,例如 Optional<List>,这不仅比较复杂以外,在语义上还代表着三种可能的返回值:
- 一个有內容的 List
- 一个空的 List
- Optional.empty()
这样容易造成程序复杂与混淆,比较好的方式是:如果真的沒有返回值,那就返回一个空的集合就好了
public List<Student> readAllStudentsInClass(String classId) {// ... return result.isEmpty() ? Collections.emptyList(): new ArrayList<>(result);
}
Map and Optional
不要將 Optional 放入 Map,例如 Map<String, Optional>,原因和上述类似,在调用 map.get(key) 的返回值:
- Optional (可能有 Student 與可能沒有 Student)
- null
像这种错误用法都会提高不必要的复杂性
更多推荐
多此一举!请不要这样用Java 8 Optional
发布评论