一、背景

最近开发过程中,身边的同事为了实现逻辑复用,定义一个私有公共方法实现逻辑复用,定义函数签名时将上游的 Optional 作为参数传递。
IDEA 给出警告,但是并没有讲清楚为什么。

效果如下:

为啥 Java 中不推荐将 Optional 当做参数使用?_开发语言

Optional 怎么使用不是本文的重点,如果想掌握可以参考 为啥 Java 中不推荐将 Optional 当做参数使用?_开发语言_02

本文主要聊为什么不让作为参数使用。

为啥 Java 中不推荐将 Optional 当做参数使用?_List_03

工作过几年的人能够发现一个规律,线上出现的异常很大比例都是空指针。

Java 8 引入 Optional 主要是为了避免出现空指针;避免代码中出现各种 null 检查等。

那么,为什么不推荐作为参数使用呢?

二、讨论

2.1 为什么不要将 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 时很容易出现空指针。

2.2 非要当做参数怎么办?

有些场景希望直接将下游的返回值作为参数传递。

模拟示例如下:

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<String>是合理的,但我们又不能将 Optional<String> 作为参数传递。

因此有如下写法:

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 也不违规,又契合将调用传递的目的。

2.3 Optional 不是万能的

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