Java 8的Optional的正确打开方式
2020-02-02 本文已影响0人
祗談風月
本文在理清Optional的基本用法的同时,附带对比Optional中的map/flatMap和stream中的map/flatMap。
问题
Java 8中的Optional号称可以避免NPE(Null PointerException) 。但在日常工作中看到很多代码中Optional的使用,是用 isPresent() 函数来判别是否为null。下面这样的判断方式,还不如直接 bean !=null
这样的判断,区别只是没有出现null这个关键词而已。更不用说链式的,不停的调用isPresent()。
如下:
if(beanOpt.isPresent()){
do somethings..
}
if(beanOpt.isPresent() && beanOpt.get().getBean1().isPresent()){
beanOpt.get().getBean1().get() ... do somethings
}
这样的代码让阅读的人不想读,修改的人不愿意修改。
这是起到了规避Null判断的作用,但是多此一举,显然,也不是Optional的正确用法。避免NPE是要Optional配合map / flatmap函数使用的。
map / flatMap的基本含义
map/flatMap在函数式编程中非常常用(map与reduce组成经典的map-reduce编程范式,flatMap是map的一种语法糖)。通常map是做元素映射,flatMap是做映射的同时去掉多层级的集合,只保留一层(所谓flat——打平)。Optional也延用了这两个名称,基本含义保持,特别的是Optional中仅一个元素,打平是去optional的包装,只保留一层。
Optional的map, flatmap的用法
- map / flatmap 函数
- map/flatMap的函数参数中,函数的输入是optional内被包装的对象,因此不需要显式处理optional的拆箱操作(用拆箱表达从optional中get元素的动作)
- map/flatMap的返回值会被自动包装到optional内返回,可以使用orElse(),ifPresent()来避免if-else的null判断
- 因为map/flatMap的这个自动拆optional的这个特性,可以对于多层optional嵌套可以链式map搞定
- 这个自动拆箱的过程不会抛出NPE,为null的元素会作为Optional.empty()向后传递
- map/flatMap的区别在于,如果函数参数中的返回值为optional,flatMap只会留一层option包装,而map会直接保留原有optional的包装(见第二段代码示例),因此flatMap的函数参数中的返回值必须是optional包装对象(这个限制在函数声明中已经限制)
这些就是optional为处理NPE的工作。除此外optional还有很多方法,都非常简洁表意,不再赘述。
// map 处理NPE
public static void main(String[] args) {
Optional<String> strOpt = Optional.of("hello world!");
Optional<String> emptyOpt = Optional.empty();
System.out.println(getStrLength(strOpt));
System.out.println(getStrLength(emptyOpt));
}
private static Integer getStrLength(Optional<String> strOpt) {
return strOpt.map(String::length).orElse(0);
}
--输出------
12
0
Process finished with exit code 0
// map / flatMap 的对比
public static void main(String[] args) {
Optional<Bean> beanOpt = Optional.of(new Bean("beanId"));
Optional<Bean> emptyOpt = Optional.empty();
System.out.println(testFlatMap(beanOpt));
System.out.println(testFlatMap(emptyOpt));
System.out.println(testMap(beanOpt));
System.out.println(testMap(emptyOpt));
}
private static String testFlatMap(Optional<Bean> strOpt) {
return strOpt.flatMap(Bean::getId).orElse("noneId");
}
private static Optional<String> testMap(Optional<Bean> strOpt) {
return strOpt.map(Bean::getId).orElse(Optional.of("noneId"));
}
static class Bean{
String id;
public Bean(String id) {
this.id = id;
}
public Optional<String> getId() {
return Optional.ofNullable(id);
}
public void setId(String id) {
this.id = id;
}
}
---输出--------
beanId
noneId
Optional[beanId]
Optional[noneId]
Process finished with exit code 0
延展:函数式中的map, flatmap
- map的基本用法
public static void main(String[] args) {
List<Bean> beans = new ArrayList<>();
beans.add(new Bean("bean1"));
beans.add(new Bean("bean2"));
beans.add(new Bean());
// 对每个beans中的bean,获取他们的id字段,转换为对应的集合
List<String> ids = beans.stream()
.map(bean -> bean.getId())
.map(opt -> opt.orElse("noneId"))
.collect(Collectors.toList());
System.out.println(ids.stream().collect(Collectors.joining(",")));
}
static class Bean{
String id;
public Bean(String id) {
this.id = id;
}
public Bean() {
}
public Optional<String> getId() {
return Optional.ofNullable(id);
}
public void setId(String id) {
this.id = id;
}
}
---- 输出-------------------
bean1,bean2,noneId
Process finished with exit code 0
- flatMap的基本用法
public static void main(String[] args) {
Stream<Bean> beanStream = Stream.of(
new Bean("bean1"),
new Bean("bean2"),
new Bean());
// 对每个beans中的bean,获取他们的id字段,转换为对应的集合
List<String> chars = beanStream
.map(bean -> bean.getId().orElse("noneId"))
.flatMap(id -> Arrays.stream(id.split("")))
.collect(Collectors.toList());
System.out.println(chars.stream().collect(Collectors.joining(",")));
}
static class Bean{
String id;
public Bean(String id) {
this.id = id;
}
public Bean() {
}
public Optional<String> getId() {
return Optional.ofNullable(id);
}
public void setId(String id) {
this.id = id;
}
}
-----输出----------------
b,e,a,n,1,b,e,a,n,2,n,o,n,e,I,d
Process finished with exit code 0