Java8 Optional

Optional和Stream虽然都是Java8的新特性,但据我观察Optional的使用频率远低于Stream,究其原因是大家对它有误解。很多人以为Optional是用来“消除”空指针的,所以当他们发现即便使用了Optional还会抛异常时,感到非常地失望,甚至是愤怒。比如当value确实为null时,直接调用Optional#get()会抛出NoSuchElementException:

// Optional#get()底层源码,当value为null时抛出NoSuchElementException,虽然不是NPE,但也是异常
public T get() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}

这实在错怪Optional了!NPE是Java语言机制的一环,单靠一个Optional类如何能够消除呢?Optional的目的不是“消除空指针”,而是优雅地做空指针“探测”。就好比给了你一个排雷工具,但你就是不按正确方法使用它,最终被炸死了,这能怪谁呢?地雷是客观存在的,不可消除。你能做的就是好好利用排雷工具,避免地雷引爆。

再说回上面的Optional#get(),很多人觉得:妈的,好不容易Optional包装了null,结果又提供了一个可能抛异常的get方法,意义何在?实际上NPE之所以让人讨厌,不仅仅因为它是一个异常(我们日常开发遇到的异常还少吗),而是因为NPE往往会掩盖确切的错误信息。举个例子:

public void method1() {
    User user = userService.getById(1L);
    this.method2(user, 999);
}

public void method2(User user, Integer point) {
    // 省略10+代码
    updatePoint(user.getId(), point);
}

抛异常的是第8行的updatePoint()方法,而实际上“错误源头”是第2行的user,这会给我们排查问题造成干扰,特别是实际项目中往往调用链路更加复杂。如果使用Optional#get(),那么在get获取user的时候就会直接报错,排查问题会简单很多!

我个人基本不用Optional#get(),更习惯用orElse或orElseThrow()处理

推荐使用场景

第一个场景就是简化空指针探测,比如:

public static String getDepartmentNameOfUser(String username) {
    ResultTO<User> resultTO = getUserByName(username);
    if (resultTO != null) {
        User user = resultTO.getData();
        if (user != null) {
            Department department = user.getDepartment();
            if (department != null) {
                return department.getName();
            }
        }
    }
    return "未知部门";
}

解决办法就是3个步骤:

  • 包装value:Optional.ofNullable()
  • 逐层安全地拆解value:map()
  • 最终返回:orElse()/orElseGet()/orElseThrow
public static String getDepartmentNameOfUser(String username) {
    return Optional.ofNullable(getUserByName(username))
            .map(ResultTO::getData)
            .map(User::getDepartment)
            .map(Department::getName)
            .orElse("未知部门");
}

其他的还可以是:

public boolean sendMessage(Long fromId, Long toId, String message) {
    // 用户校验:如果用户不存在,直接抛异常
    User user = Optional.ofNullable(userService.getUserById(fromId))
            .orElseThrow(() -> new BizException(ErrorEnumCode.USER_NOT_EXIST));
    
    // 组装数据并发送...
}
public List<String> listSubCities(String provinceCode) {
    // 查到就返回,查不到就返回替代值(对于集合而言,尽量返回空集合)
    return Optional.ofNullable(getCitiesByPid(provinceCode)).orElse(new ArrayList<String>());
}

另外,如果你需要对返回值进行判断,比如结果是否大于某个值等,可以使用Optional的filter方法:

public class OptionalFilterTest {

    public static void main(String[] args) {

        // 需求:调用getUser()得到person,并且person的age大于18才返回username,否则返回不存在
        
        // 普通的写法(如果层级深一点会很难看)
        Person user = getUser();
        if (user != null && user.getAge() > 18) {
            System.out.println(user.getName());
        } else {
            System.out.println("不存在");
        }

        // 你尝试用map(),但你发现直接返回username了,你甚至无法再次判断是否age>18
        String username1 = Optional.ofNullable(getUser())
                .map(Person::getName)
                .orElse("不存在");
        System.out.println("username1 = " + username1);

        // 引入filter()
        String username2 = Optional.ofNullable(getUser())
                .filter(person -> person.getAge() > 18)
                .map(Person::getName)
                .orElse("不存在");
        System.out.println("username2 = " + username2);
    }

    public static Person getUser() {
        if (RandomUtils.nextBoolean()) {
            return null;
        } else {
            Person person = new Person();
            person.setName("鲍勃");
            // commons.lang3
            person.setAge(RandomUtils.nextInt(0, 50));
            return person;
        }
    }

    @Data
    static class Person {
        private String name;
        private Integer age;
    }
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sunny_yiyi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值