最近开发过程中,身边的同事为了实现逻辑复用,定义一个私有公共方法实现逻辑复用,定义函数签名时将上游的 Optional
作为参数传递。
IDEA 给出警告,但是并没有讲清楚为什么。
效果如下:
Optional
怎么使用不是本文的重点,如果想掌握可以参考 《Java 8 实战》^「1」 自行学习。
本文主要聊为什么不让作为参数使用。
工作过几年的人能够发现一个规律,线上出现的异常很大比例都是空指针。
Java 8 引入 Optional 主要是为了避免出现空指针;避免代码中出现各种 null 检查等。
那么,为什么不推荐作为参数使用呢?
Optional
作为参数如果将 Optional 当做参数使用,那么本身可传递 null, 依然需要进行判空再使用。 并不能有效避免空指针,甚至带来额外的判断。
案例1: 直接使用 String :
public String doSomething(String name) {
if (name == null) {
return "你好";
} else {
return "你好 " + name;
}
}
使用 Optional 作参数:
public String doSomething(Optional<String> name) {
if (name == null || !name.isPresent()) {
return "你好";
} else {
return "你好" + name;
}
}
示例2: 由于我们通常都是将 Optional 当做返回值使用,潜意识认为不会传递 null, 通常就直接使用:
public static List<Person> search(List<Person> people, String name, Optional<Integer> age) {
if(CollectionUtils.isEmpty(people)|| null == name ){
return new ArrayList<>();
}
return people.stream()
.filter(p -> p.getName().equals(name))
.filter(p -> p.getAge().get() >= age.orElse(0))
.collect(Collectors.toList());
}
如果代码比较复杂,其他程序员不容易注意到这点,他可能会认为不需要校验 age ,因此就传 null:
someObject.search(people, "Peter", null);
结果造成了空指针!!
public static List<Person> search(List<Person> people, String name, Integer age) {
if(CollectionUtils.isEmpty(people)|| null == name ){
return new ArrayList<>();
}
final Integer ageFilter = age != null ? age : 0;
return people.stream()
.filter(p -> p.getName().equals(name))
.filter(p -> p.getAge().get() >= ageFilter)
.collect(Collectors.toList());
}
因此,尽量避免将 Optional 作为参数使用。
本质上是 Optional 作参数时,上游通常可以自己构建 Optional 或者取下游某个调用的返回值传递。
当使用某个调用返回值传递时,通常不会出现空指针,但是自己去执行调用传递 null 时很容易出现空指针。
有些场景希望直接将下游的返回值作为参数传递。
模拟示例如下:
private static String first(String someParam){
return something( "first", someParam, invokeSomeFunction(someParam));
}
private static String second(String someParam){
return something( "second", someParam, invokeOtherFunction(someParam));
}
private static <T> T something(String name ,String someParam,Optional<T> optional){
// 各种公共逻辑
return optional.get();
}
// 模拟下游接口1
private static Optional<String> invokeSomeFunction(String someParam){
return Optional.of(someParam);
}
// 模拟下游接口2
private static Optional<String> invokeOtherFunction(String someParam){
return Optional.of(someParam);
}
下游返回 Optional
是合理的,但我们又不能将 Optional
作为参数传递。
因此有如下写法:
private static String first(String someParam){
return something( "first", someParam, invokeSomeFunction(someParam).orElse(null));
}
private static String second(String someParam){
return something( "second", someParam, invokeOtherFunction(someParam).orElse(null));
}
private static <T> T something(String name ,String someParam,T param){
// 各种公共逻辑
return null;
}
如果自定义方法过多,都要 orElse 去转为非 Optional 对象,显然不太优雅。
其实,这种场景本质上是希望将调用作为参数传递下去,因此想到了直接使用 Supplier
或者 Function
等。
private static String first(String someParam){
return something( "first", someParam, ()->invokeSomeFunction(someParam));
}
private static String second(String someParam){
return something( "second", someParam, ()->invokeOtherFunction(someParam));
}
private static <T> T something(String name , String someParam, Supplier<Optional<T>> optional){
// 各种公共逻辑
return null;
}
这样 Optional
依然是作为返回值使用,参数是方法调用 Supplier
也不违规,又契合将调用传递的目的。
Optional
虽然能够减少空指针,但是滥用也会降低代码可读性。
Optional
本身没有实现序列化接口,做属性时,如果使用 JDK 序列化将会报错。
可以使用 guava 包里的 Optional
类替代。
【建议】不建议将 Optional 作为参数,容易造成空指针和误解,这和 Optional 的目的相违背。如果是想传递某个调用,请使用 Supplier。 【建议】不建议将 Optional 作为属性,非要用建议使用 guava 包的 Optional 类。
[1] 厄马(Raoul-Gabriel Urma) / 弗斯科(Mario Fusco) / 米克罗夫特(Alan Mycroft)《Java 8 实战》.人民邮电出版社 [2] https://rules.sonarsource.com/java/RSPEC-3553 [3] https://www.baeldung.com/java-optional