- 注:本文仅讨论Java中如何避免一堆if来对Null值进行判空的操作,并不考虑方案的可行性以及合理性。
背景
在程序中,我们经常会遇到这样的场景:
1
2
3
4
5
6
7
8
9
10
11
12
13public String getPersonAddress(int personId){
return personMap.get(personId).getPosition().getAddress();
}
private static Map<Integer, Person> personMap;
@Data
public static class Person{
private int personId;
private Position position;
}
@Data
public static class Position{
private String address;
}在
getPersonAddress
中,我们多层级的调用了对象的属性,来获取一个人员的地址,但是这在任意一个节点都可能会导致空指针异常。因此,需要做如下改动:
1
2
3
4
5
6
7
8
9
10
11public String getPersonAddress(int personId){
String address = UNDEFINED_ADDRESS;
Person person = personMap.get(personId);
if(person != null){
Position position = person.getPosition();
if(position != null){
address = position.getAddress();
}
}
return address;
}这样就会导致在Java程序中if的层级很深。而在C#中,提供了
?
和??
操作符(Elvis操作符),我们可以很简单的通过问号表达式来完成相应的操作(C#中如果字典没找到值PersonDic[personId]
这个地方可能会抛异常,我们假定这里重写了索引器):1
2
3public string GetPersonAddress(int personId){
return PersonDic[personId]?.GetPosition()?.GetAddress() ?? UndefinedAddress;
}在Java 7的时候,有对此的提案,但是被否决了,因此大概率短时间内的Java发行版是不会包含相类似的操作符了。下面是一些Java中可以用到的手段。
方案
- 感觉也没啥特别好的方案,基本上都只能依靠函数接口解决。
三元表达式
没啥说的
1
2
3
4public static String getPersonAddress(int personId) {
Person person;
return (person = personMap.get(personId)) != null && person.getPosition() != null ? person.getPosition().getAddress() : UNDEFINED_ADDRESS;
}
Optional接口
在Java 8的时候引入了Optional接口(Guava包老早就有了),它对变量做了一层包装,可以让外部调用人员知道这个地方的返回值可能是空的(有点类似
@Nullable
的 作用),那么,我们可以使用Optional接口来进行简写。注:
Optional#ofNullable
在参数为空的时候,返回的是内部的空Optional
,所以这里不会有额外的Optional
创建的开销,性能上可以放心。无非就是多了几次Optional#isPresent
方法的调用而已。1
2
3
4
5
6public String getPersonAddress(int personId){
return Optional.ofNullable(personMap.get(personId))
.map(Person::getPosition)
.map(Position::getAddress)
.orElse(UNDEFINED_ADDRESS);
}
手写一个管道函数接口
总体来说和Optional接口差不多,在Java Functional接口的基础上添加了一个为null的判断而已。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class Pipe<T> {
private T object;
private Pipe(T t) {
object = t;
}
public static<T> Pipe<T> of(T t) {
return new Pipe<>(t);
}
public <S> Pipe<S> after(Function<? super T, ? extends S> plumber) {
return new Pipe<>(object == null ? null : plumber.apply(object));
}
public T get() {
return object;
}
public T orElse(T other) {
return object == null ? other : object;
}
}使用:
1
2
3
4
5
6public String getPersonAddress(int personId){
return Pipe.of(personMap.get(personId))
.after(Person::getPosition)
.after(Position::getAddress)
.orElse(UNDEFINED_ADDRESS);
}
函数接口+异常
由于会有抛出空指针异常 - > 捕获指针异常的操作。会降低程序的性能。不过就代码来看,确实是最少的。
1
2
3
4
5
6
7private static <T, R> R getValue(T t, Function<T, R> funct, R defaultValue) {
try {
return funct.apply(t);
} catch (NullPointerException e) {
return defaultValue;
}
}使用:
1
2
3public static String getPersonAddress(int personId){
return getValue(personId, id -> personMap.get(id).getPosition().getAddress(), UNDEFINED_ADDRESS);
}其实本质上就是类似:
1
2
3
4
5
6
7
8
9public static String getPersonAddress(int personId) {
String address;
try {
address = personMap.get(personId).getPosition().getAddress();
} catch (NullPointerException e) {
address = UNDEFINED_ADDRESS;
}
return address;
}
Java编译插件
- 可以通过修改Java的编译行为达到来使Java支持Elvis操作符,算是个骚操作,这种还是别上生产。
- Github地址
扩充
如果我们还有一种场景,就是如果要明确的知道那个值为空(或者说为空的时候返回一个提示,那么就可以这么干)。
- 注:个人觉得是不如直接用if的。
定义一个工具类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public class NullableWrapper<T>{
private String nullMsg;
private T data;
public static <T> NullableWrapper<T> of(String nullMsg, T data){
return new NullableWrapper<>(nullMsg, data);
}
@SuppressWarnings("unchecked")
public <R> NullableWrapper<R> after(String nullMsg, Function<? super T, ? extends R> dataFunc){
return this.nullMsg != null ? (NullableWrapper<R>) this : of(nullMsg, dataFunc.apply(data));
}
private NullableWrapper(String nullMsg, T data){
if(data == null){
this.nullMsg = nullMsg;
} else {
this.data = data;
}
}
public String getNullMsg() {
return nullMsg;
}
public T getData() {
return data;
}
}使用:
1
2
3
4
5
6public static String getPersonAddress(int personId){
NullableWrapper<String> result = NullableWrapper.of("用户为空", personMap.get(personId))
.after("地址为空", Person::getPosition)
.after("地址信息为空", Position::getAddress);
return result.getNullMsg() != null ? result.getNullMsg() : result.getData();
}