Java8:Optional - 解救分支地狱

相信任何一个Java开发者都会遇到NPE(即NullPointerException),而为了避免出现NPE,通常的方法是在访问对象前进行判空,即使用分支语句进行判断if (xx != null)。
但是,过多的分支语句(尤其是和业务逻辑结合起来后),会导致代码可读性和可维护性的下降,因此需要减少它的出现。还好Java8提供了Optional这个工具,能够消除大部分判空和一部分普通判断。

Optional

Optional<T>代表一种有或为空的数据,使用Optional.ofNullable()可以创建一个接受null的Optional,而使用Optional.of()创建Optional时,传入参数为null就会立即抛出NPE。本文并非介绍Optional中各个方法的使用,而是在具体情形下的Optional取代if的实例。

替代判空分支

绝大部分的判空分支可以被Optional替代。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Object sth = ...;
// 为空抛出业务异常
if(sth == null){
throw new Exception...
}
// 或者提供默认值
if(sth == null){
sth = ...
}
// 从另外一个来源获取
if(sth == null){
sth = getFrom...()
}

// 不为空时,干点别的
if(sth != null){
...
}
// 不为空就,为空则(Java 9 以上适用)
if(sth != null){
...
}else{
...
}

均可使用Optional的对应方法重构:

1
2
3
4
5
6
7
8
9
10
// 为空抛出业务异常
Optional.ofNullable(...).orElseThrow(() -> new Exception...);
// 或者提供默认值
Optional.ofNullable(...).orElse(xxx);
// 从另外一个来源获取
Optional.ofNullable(...).orElseGet(() -> getFrom...());
// 不为空时,干点别的
Optional.ofNullable(...).ifPresent(x -> ...);
// 不为空就,为空则(Java 9 以上适用)
Optional.ofNullable(...).ifPresentOrElse(x -> ...,() -> ...);
安全链式调用
1
Object result = sth.getA().getB().getC().getD().getE();

链式调用写起来确实爽,可是用着的时候其中如果有一个是null,那就要抓狂了😫,而且由于null的字段可能不确定,因此可能需要嵌套分支或者使用一个超长条件分支判空。

1
2
3
4
5
6
7
8
9
10
11
if(sth.getA() != null){
Object sth2 = sth.getA();
if(sth2.getB() != null){
...
}
}
// 或者
if(sth.getA() != null && sth.getA().getB() != null ...){
return ...
}
// 其他的不举例了

使用Optional的map方法可以避免这一点:

1
Optional.ofNullable(sth).map(xxx::getA).map(yyy::getB)...

复制代码
map会自动对返回值包装成Optional,与map相对的还有flatMap方法,它只接受返回为Optional的方法引用。引入flatMap是为了防止出现多重包装,例如Optional

1
2
3
4
5
6
7
public class X{
public Optional<Y> getName(){
return ...
}
}
X sth = ...
Optional.ofNullable(sth).flatMap(X::getName)...
替代部分条件判断分支

Optional也能替代一些简单的Bean条件判断,利用的是filter这个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
XXX sth = ...
if(sth.getB() > 10){
return sth.getB().getC();
}
if(sth.getA() > 5){
throw new ...
}

// 使用Optional进行替代
// 注意这里这样子的话getC()的返回值就不能是null了,否则会抛出NoSuchElementException,要不把返回值类型改为Optional<T>?
return Optional.of(sth).map(XXX::getB).filter(x -> x > 10).map(XXX:getB).get();
// 注意这里是orElse,所以filter要反过来!
Optional.of(sth).map(XXX::getA).filter(x -> x <= 5).orElseThrow(() -> new ...);
使用if的优秀实践

即使是必须使用if的情况,也存在着一些技巧可以优化代码的结构。一个原则是:尽量减少代码嵌套,代码短的分支路径写在if下,必要时反转if。
一种思想是将if作为判断特殊条件的过滤器,及时判断「异常状态」并返回,以登录为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if(checkPassword(password,userInput)){
// 登录验证密码成功后可能会进行一堆操作
...
}else{
// 如果上面的代码非常多,不回顾下不知道是什么的else
throw new Exception("密码错误")
}
// 但如果是下面的写法,可能会好多了
if(!checkPassword(password,userInput)){
// 代码短的分支路径写在if下
// 调转if的条件,把验证密码错误作为一个特例进行过滤
throw new Exception("密码错误")
}
// 登录验证密码成功后的一堆操作,就不在分支里面,当作正常处理
...

From

评论

:D 一言句子获取中...