添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

API内部使用null,API返回值可能为空时用Optional包装。

Java 8由于Lambda的引入,经常会写出链式的代码,如果中间某一步返回null,后面就没法执行了,直接NPE(NullPointerException),而Stream等Java 8的API都可能出现这种情况,所以设计了Optional使程序不会NPE。后文详细介绍。

这里有个误区,不是说到了Java 8就要把所有写null的地方都改写了,Optional和null有关系,但不是替换关系,官方有这么一句话:

Optional关注的是存在或不存在,而不是空或非空。
The idea of Optional is to think about presence or absence of something distinctly from null reference or not.

这话写成中文有点迷,所以我把英文也直接写过来了。直接解释容易越说越乱,所以接下来以各种例子来说明。

为什么要使用

先说说它诞生的原因,看下面这段代码。

String getCustomerNameByID(List<Customer> custList, int custID) {
    return custList.stream()
                   .search(c -> c.getID() == custID)
                   .getName();

简洁明了。但是问题来了,其实是个非常常见的问题,那就是如果search的时候没有找到符合条件的Customer怎么办?返回null呗,然后就NPE了。所以谨慎的同行们都是时刻检查null的。

String getCustomerNameByID(List<Customer> custList, int custID) {
    Customer cust = custList.stream()
                            .search(c -> c.getID() == custID);
    return (cust != null) ? cust.getName() : "UNKNOWN";

但是这一点也不优雅了。而且我见过一个段子如下:

if(a != null) {
    B b = a.getB();
    if(b != null) {
        C c = b.getC();
        if(c != null) {
            .....

简直就是null的地狱。发明null的大神说过这是他最昂贵的错误一点也不为过。
所以Java的官方人员就将stream等一部分API中返回不存在时的值做成Optional。一是提醒Java开发者此处可能不存在,二是可以顺畅的写链式表达式而不用中断。

上面的代码就带来第一种优雅的使用方法,filter,findFirst,map的组合,代码改写如下:

String getCustomerNameByID(List<Customer> custList, int custID) {
    return custList.stream()
                   .filter(c -> c.getID() == custID)
                   .findFirst()
                   .map(Customer::getName)
                   .orElse("UNKNOWN");

这里面还是有些技巧的,所以需要把Optional的API和返回Optional的API都练习一下,要不然很容易越写越乱。

然后是第二种比较通用的优雅方法,ifPresent

getTask(...).ifPresent(task -> executor.runTask(task));

一定要使用ifPresent,因为isPresent这个方法又回到了if(a != null)这样的显式判断逻辑里了,实在没有必要。

第三个就是Optional.of(T),Optional.empty()
Optional.empty()返回一个空Optional,Optional.of(T)可以包装一个非空的对象,如果为空则抛异常,不过不是NPE,是NoSuchElement。

乱用的场景

说了这些好处,自然要说说没有理解Optional本质后的滥用的场景。
首先就是在不该替换null的地方大量去替换。

String process(String s) {
    return Optional.ofNullable(s).orElseGet(this::getDefault);

这一大串包装,进出都是String,完全是在浪费功夫。看看原始的写法。

String process(String s) {
    return (s != null) ? s : getDefault();

很简洁了。Optional不是null的替换品,因为这里没有链式的程序,强行包装增加了很多开销,得不偿失。

第二点就是在集合,属性,方法参数中大量使用Optional。
官方不推荐在上述种类的元素中使用Optional,因为这些的内容如果能为null,就不应该存在,强行装着Optional占着内存没有必要。如果出现这种情况,一定要再仔细思考是否真的有必要,不能为了用而用。

和老代码的整合

问题来了。如果是新写代码一切都还好说,老代码怎么办。如上面所说Optional的开销大,不适宜全部重写,当然也不切实际。所以老代码就不用改写了。不过新老代码可能要互相交互啊,我的老代码的API返回的只有null,新代码就不处理了?当然不是,这之中就需要一些过度代码,如下两个方法:

Optional.ofNullable(null);
Optional.orElse(null);

第一个可以将null包装进Optional。第二个可以返回null。这两种写法如果不是为了兼容老代码是极力不推荐的,因为这又和null纠缠在一起了。

忘记Optional.get()

官方人员说了,发明这个方法是他整个Java 8最后悔的事。确实啊,这个方法使Optional的意义大打折扣,因为如果Optional为空,直接使用就带来一个异常,必须和isPresent结合使用,不能单独使用的方法就没有存在的意义了。所以官方说了在Java 9中会废弃这个方法。

所以总的来说,这是一个在某些场合下使用的一个包装类(至少目前还是包装,以后好像会往原始类型发展),核心使用方法请参照开头那句简练的话。弄清楚它和null的关系使用起来就会得心应手了。

Q.E.D.