如何正确使用 Optional
Java 8 借鉴 Guava ,推出了 Optional ,目的是从 JDK 级别解决NPE(nullpointerexception) 问题。
1. 一个编程技巧
宁可为空( empty ),不要为空( null )。
如果一个方法需要返回字符串或数组、集合,在逻辑上无返回的情况下,要返回空串、空数组、空集合,而不是 null 。这么干的好处在于,能避免掉后续的 NPE 。
2. Optional 作返回值
上述编程技巧的使用有一个局限性:如果一个方法的返回值不是 String、数组、集合,那么你就没有办法使用这种编程技巧。
Optional 就是基于这个编程技巧提出一个方案:你把 Optional 当作一个容器,将逻辑上想要返回的数据「装」进 Optional 中,然后返回这个 Optional 对象。
这样就相当于在强制性要求这个方法返回一个集合( 当然,这个集合中放的仅仅是一个数据 ),如果你这么干了,那么你就会发现你的这个方法就符合了上述那个编程技巧所要求的使用条件:方法返回一个集合。
3. Optional 返回和普通返回
Student getById(Long id);
Optional <Student> getById(Long id);
一个方法是直接返回一个 Student 对象,还是将 Student 对象「装」进 Optional 容器中再返回,暗示着不同的涵义:
直接返回 Student 对象,意味着由 getById 方法的作者来确保不会返回 null,getById 的使用者没有判空的责任,如果在使用者没有判空的情况下出现了 NPE 问题,那么责任在方法的作者。
返回 Optional<Student> 对象,意味着由 getById 方法的使用者来判空,Optional 这个容器中可能放着一个 Student 对象,可能什么都没有,这个事情需要方法的使用者自己确定。如果在使用者没有判空的情况下出现了 NPE 问题,那么责任在方法的使用者,而非作者。
4. 创建 Optional 对象
创建 Optional 对象由 3 种方式:
Optional.empty()
它是用来构造一个 Optional 空容器。实际上使用它的机会几乎没有。
Optional.of(obj)
它要求传入的 obj 不能是 null 值的, 否则还没开始进入角色就倒在了 NullPointerException 异常上了。
Optional.ofNullable(obj)
它以更智能、宽容的方式来构造一个 Optional 实例,来者不拒。传 null 进到就等价于调用 Optional.empty(), 非 null 就等价于调用 Optional.of(obj) 。
使用 Optional.of(obj) 来构造 Optional 实例的场景:
当我们非常的明确将要传给 Optional.of(obj) 的 obj 参数不可能为 null 时, 比如它是一个刚 new 出来的对象(例如,Optional.of(new User(...))), 或者是一个非 null 常量时;
逻辑上 obj 明确不允许为 null 。一旦为 null ,立即报错,抛出 NPE 。不允许接续执行。
5. Optional 对象的判空
如何判断一个 Optional 容器中是否真的有内容( 而不是 null )?标准的错误答案如下:
假定我们有一个实例 Optional<User> user:
if( user.isPresent()){...}else{...}
应该用如下方式使用:
存在即返回, 无则提供默认值:
return user.orElse(null);
return user.orElse(UNKNOWN_USER);
存在即返回, 无则由函数来产生:
return user.orElseGet(()->fetchAUserFromDatabase());
6. map 函数隆重登场
如果有一个很「深」的引用关系会被我们使用,例如,打印 tom 同学的老师的儿子的狗的名字:
System.out.println( tom.getTeacher().getSon().getDog().getName());
你会发现这里有大量的 NPE 风险,稳妥期间,这段代码可能会写成这样:
if( tom !=null){
if( tom.getTeacher()!=null){
if( tom.getTeacher().getSon()!=null){
if( tom.getTeacher().getSon().getDog()!=null){
System.out.println( tom.getTeacher().getSon().getDog().getName());
}
}
}
}
这样才能确保这行代码不会出现 NPE 。很显然这么写太繁琐了。如果这些方法返回的都是 Optional ,那么,借助 Optional 的 map 方法,你可以写成如下形式:
tom.map(s -> u.getTeacher())
.map(t -> t.getSon())
.map(s -> t.getDog())
.map(d -> t.getName()).orElse(null);
提示:这里的 map 的使用非常类似 stream 中的 map 形变函数。
看到这,点个赞吧!!!