Java8 Optional

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

烦人的空指针

请大家观察下面这段代码:

public class NullPointerTest {
    /**
     * 需求:根据用户名查找该用户所在的部门名称
     *
     * @param args
     */
    public static void main(String[] args) {
        String departmentNameOfUser = getDepartmentNameOfUser("test");
        System.out.println(departmentNameOfUser);
    }

    /**
     * 假设这是A-Service的服务
     * 这一步很烦!!!
     *
     * @param username
     * @return
     */
    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 "未知部门";
    }

    /**
     * 假设这是B-Service的服务(不用关注具体逻辑,就是随机模拟返回值,可能为null)
     *
     * @param username
     * @return
     */
    public static ResultTO<User> getUserByName(String username) {
        if (username == null || "".equals(username)) {
            return null;
        }

        Department department;
        User user;

        if (ThreadLocalRandom.current().nextBoolean()) {
            department = new Department("总裁办", 10086);
        } else {
            department = null;
        }
        if (ThreadLocalRandom.current().nextBoolean()) {
            user = new User("周董", 18, department);
            user.setDepartment(department);
        } else {
            user = null;
        }

        return ResultTO.buildSuccess(user);
    }


    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    static class User {
        private String name;
        private Integer age;
        private Department department;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    static class Department {
        private String name;
        private Integer code;
    }
    
    @Getter
    @Setter
    static class ResultTO<T> implements Serializable {

        private Boolean success;
        private String message;
        private T data;

        public static <T> ResultTO<T> buildSuccess(T data) {
            ResultTO<T> result = new ResultTO<>();
            result.setSuccess(true);
            result.setMessage("success");
            result.setData(data);
            return result;
        }

        public static <T> ResultTO<T> buildFailed(String message) {
            ResultTO<T> result = new ResultTO<>();
            result.setSuccess(false);
            result.setMessage(message);
            result.setData(null);
            return result;
        }
    }
}

你会发现,如果一个POJO的层级过深而且恰好作为返回值返回时,调用者将苦不堪言,为了避免空指针不得不写一大堆的if判断,也就是被迫做空指针探测。

一种较为通用的做法是采用“卫函数”:

/**
 * 假设这是A-Service的服务
 * 这一步很烦!!!
 *
 * @param mx
 * @return
 */
public static String getDepartmentNameOfUser(String username) {
    ResultTO<User> resultTO = getUserByName(username);
    if (resultTO == null) {
        return "ResultTO为空";
    }
    
    User user = resultTO.getData();
    if (user == null) {
        return "User为空";
    }
    
    Department department = user.getDepartment();
    if (department == null) {
        return "Department为空";
    }
    
    return department.getName();
}

虽然避免了过深的if嵌套,逻辑稍微清晰一点,但还是很啰嗦。

封装NullWrapper

我们来尝试封装一个工具类,希望能简化NullPointerException的探测工作。

核心思想

设计一个Wrapper,内部有一个T value字段,用来接收返回值,这样就能把null包裹在内部,稍微安全了一些,因为Wrapper肯定不为null:new Wrapper(value).getXxx()。

但这还不够!因为如果这个value真的是null,而外界getValue()后再次调用的话,仍然会发生NullPointerException。

怎么处理?

其实答案已经出现过了:再次把返回值包装成Wrapper即可。

比如:

public static void main(String[] agrs) {
    Wrapper resultWrapper = new Wrapper(getDepartmentnameByName(username));
    Wrapper userWrapper = new Wrapper(resultWrapper.get()); // resultTO可能为null,所以再次包装
    Wrapper departmentWrapper = new Wrapper(userWrapper.get());
    Wrapper departmentNameWrapper = new Wrapper(departmentWrapper.get());
    // ...
}

但上面的代码其实“很傻”:

从resultWrapper取出内部的值以后,又塞进新的wrapper...其实是没有意义的,最后那个departmentNameWrapper内部塞的其实还是username

我们应该对value进行判断,当value不为null时往下取一层,这样才能最终一层层剥开value得到最终的值:

public <U> NullWrapper<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (value是否为null)
        // 如果为null,直接用Wrapper包装null,避免直接暴露导致空指针
        return new Wrapper(null);
    else {
        // 如果不为null,那么调用传入的映射规则,【剥掉一层嵌套】,把下一级取出来重新包装为Wrapper
        return new Wrapper(mapper.apply(value));
    }
}

由于不论value是否为null,最终返回的都是Wrapper,而Wrapper有map(),所以可以形成链式调用:

也就是说,每一次map()其实都有可能剥掉一层嵌套,而且过程中不会发生空指针异常!

// 初始化包裹,得到Wrapper
Wrapper wrapper = new Wrapper(firstLevelValue);
// 调用Wrapper#map()尝试向下剥开每一级的嵌套,由于map()返回的也是Wrapper对象,可以链式调用
wrapper
.map(firstLevelValue -> firstLevelValue.getSecondLevelValue)
.map(secondLevelValue -> secondLevelValue.getThirdLevelValue)
.map(...)

为什么是有可能?

我们来考虑两种极端的情况:

  • 如果每一级value都为null,其实每次map()都是对null进行包装传递、取出、再包装,并没有剥开任何嵌套(null值不存在嵌套)
  • 如果每一级value都不为null,每次map()都剥开一层,这是最理想的效果,离最终需要的value越来越近

第一种情况,其实也没什么,无非就是多new几个Wrapper对象(似乎有点浪费?后面再优化)。第二种情况,符合我们的预期,但也不能每次剥开又给套上Wrapper吧,丑媳妇最终还是要见公婆。所以,除了map()方法,Wrapper还需要额外提供一个最终获取真实value的方法,比如Wrapper#orElse(T other),它允许调用者得到未包装的value,但是!有个条件:调用orElse()时必须传一个备用的值,如果value真的为null,则返回备用值代替null。如果你实在没有备用值,也可以使用null作为备用值,但使用时务必谨慎。

上面讲述的就是Wrapper工具类的核心思想,接着让我们一起来封装一下!

为了更容易理解,我们先封装一个Mini版:

public final class MiniNullWrapper<T> {

    /**
     * 实际值
     */
    private final T value;

    /**
     * 无参构造,默认包装null
     */
    private MiniNullWrapper() {
        this.value = null;
    }

    /**
     * 有参构造器,用来包装外部传入的value
     *
     * @param value
     */
    private MiniNullWrapper(T value) {
        this.value = value;
    }

    /**
     * 静态方法,返回一个包装了null的Wrapper
     *
     * @param <T>
     * @return
     */
    public static <T> MiniNullWrapper<T> empty() {
        // 调用无参构造,返回包装了null的Wrapper
        System.out.println("由于value为null,直接返回包装了null的Wrapper,让流程继续往下");
        return new MiniNullWrapper<>();
    }

    /**
     * 静态方法,返回一个包装了value的Wrapper(value可能为null)
     *
     * @param value
     * @param <T>
     * @return
     */
    public static <T> MiniNullWrapper<T> ofNullable(T value) {
        // 调用有参构造,返回包装了value的Wrapper
        return new MiniNullWrapper<>(value);
    }

    /**
     * 核心方法:
     * 1.如果value为null,直接返回空的Wrapper
     * 2.如果value不为null,则使用mapper对value进行处理,往下剥一层(这是关键,一有机会就要往下剥一层,否则就是原地踏步)
     *
     * @param mapper
     * @param <U>
     * @return
     */
    public <U> MiniNullWrapper<U> map(Function<T, U> mapper) {
        Objects.requireNonNull(mapper);
        if (value == null)
            // 按上面说的,如果value为null,我都不处理了。但为了调用者拿到返回值后不会发生空指针,需要用Wrapper包装一下
            return MiniNullWrapper.empty();
        else {
            /*
             * value不为null,那么就要想尽办法将它剥去一层皮。
             * 由于此时value不为null,即使mapper的apply方法要做的操作是 value.getXxx()/value.setXxx(),都不会空指针
             * mapper.apply(value)处理后的结果继续用Wrapper包装,此时【新的wrapper里的value】是处理后的数据(下一层)
             * */
            return MiniNullWrapper.ofNullable(mapper.apply(value));
        }
    }

    /**
     * 终端操作,决定勇敢一次。当你做好面对外面的世界时,就要卸下伪装:直接把value丢出去。
     * 但为了不祸害别人,给个备选值:other。当你确实是null时,返回other。
     *
     * @param other
     * @return
     */
    public T orElse(T other) {
        return value != null ? value : other;
    }

    // -------- 测试方法 ---------

    public static void main(String[] args) {
        // 全部不为null
        Son sonNotNull = new Son("大头儿子");
        Father fatherNotNull = new Father();
        fatherNotNull.setSon(sonNotNull);
        GrandPa grandPaNotNull = new GrandPa();
        grandPaNotNull.setFather(fatherNotNull);
        // 处理grandPa,观察map()中的处理方法有没有被调用
        String sonName1 = MiniNullWrapper.ofNullable(grandPaNotNull)
                .map(grandPa -> grandPa.getFather())
                .map(father -> father.getSon())
                .map(son -> son.getName())
                .orElse("没得到儿子的名字");

        // 全部为null
//        GrandPa grandPaNull = new GrandPa();
//        grandPaNull.setFather(null);
//        // 处理grandPa,观察map(),你会发现,从grandPa取出father后,由于发现是null,所以father->father.getSon()不会执行,避免了空指针
//        String sonName2 = MiniNullWrapper.ofNullable(grandPaNull)
//                .map(grandPa -> grandPa.getFather())
//                .map(father -> father.getSon())
//                .map(son -> son.getName())
//                .orElse("没得到儿子的名字");
    }

    // ---- 没啥实质内容,就是几个简单的类,我在getter方法中打印了一些信息 ----
    static class GrandPa {
        private Father father;

        public Father getFather() {
            System.out.println("GrandPa#getFather被调用了");
            return father;
        }

        public void setFather(Father father) {
            this.father = father;
        }
    }

    static class Father {
        private Son son;

        public Son getSon() {
            System.out.println("Father#getSon被调用了");
            return son;
        }

        public void setSon(Son son) {
            this.son = son;
        }
    }

    @AllArgsConstructor
    static class Son {
        private String name;

        public String getName() {
            System.out.println("Son#getName被调用了");
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

}

大家先把上面的代码消化了,然后再重新思考下面的问题

MiniNullWrapper.ofNullable(result)
    .map(step1)
    .map(step2) // 是否曾担心,在这一步突然出现null,然后空指针异常呢?
    .map(step3)
    .orElse(step4);

学习MiniNullWrapper后,是否已经有答案了呢?

所以,当你以后使用NullWrapper的map()时,大胆地往下“剥”,不要担心中间是否会出现null导致链路中断:如果value为null,压根不会执行你传入的mapper.apply(),而是创建空的Wrapper继续往下传递!

工具类封装

来看看完全版的NullWrapper吧~

/**
 * 工具类,用于简化空指针的探测工作
 * 核心思想:将可能为null的value包装成NullWrapper对象,那么调用nullWrapper.xxx()方法肯定就不会发生NullPointerException
 * 核心方法:map()
 * <p>
 * map()方法实际操作的是nullWrapper对象内部的value,将value映射为指定的值(比如下一级的字段)
 * 如果value为null,调用empty()方法,返回包装了null的NullWrapper对象
 * 如果value不为null,执行mapper.apply(value)得到结果并调用ofNullable(T newValue)方法,返回包装了newValue的NullWrapper对象
 *
 * @param <T>
 */
public final class NullWrapper<T> {

    /**
     * 配合{@link NullWrapper#empty()}使用,返回一个包装了null的NullWrapper
     * 主要是为了避免每次重复创建空的Wrapper!
     */
    private static final NullWrapper<?> EMPTY = new NullWrapper<>();

    /**
     * 实际值
     */
    private final T value;

    /**
     * 构造器,包装null
     */
    private NullWrapper() {
        this.value = null;
    }

    /**
     * 构造器,包装指定的【非空值】
     * 如果传入null会抛NullPointerException
     *
     * @param value
     */
    private NullWrapper(T value) {
        this.value = Objects.requireNonNull(value);
    }

    /**
     * 静态方法,返回一个包装了null的NullWrapper
     *
     * @param <T>
     * @return
     */
    public static <T> NullWrapper<T> empty() {
        @SuppressWarnings("unchecked")
        NullWrapper<T> t = (NullWrapper<T>) EMPTY;
        return t;
    }

    /**
     * 静态方法,包装指定的【非空值】
     * 如果传入null会抛异常NullPointerException
     *
     * @param value
     * @param <T>
     * @return
     */
    public static <T> NullWrapper<T> of(T value) {
        return new NullWrapper<>(value);
    }

    /**
     * 静态方法,包装指定值,允许null。当传入null时,会调用empty()方法返回EMPTY对象
     *
     * @param value
     * @param <T>
     * @return
     */
    public static <T> NullWrapper<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }

    /**
     * 是否有值(非null)
     *
     * @return
     */
    public boolean isPresent() {
        return value != null;
    }

    /**
     * 核心方法
     * 调用者:NullWrapper对象,内部含有value,可能为null,也可能不为null
 	 * 如果value为null,调用empty()方法,返回包装了null的NullWrapper对象
 	 * 如果value不为null,执行mapper.apply(value)得到结果并调用ofNullable(T newValue)方法,返回包装了newValue的NullWrapper对象
     * 简而言之,每次调用map(),都有可能剥掉一层外壳,也意味着躲过了一次潜在的NullPointerException
     *
     * @param mapper
     * @param <U>
     * @return
     */
    public <U> NullWrapper<U> map(Function<T, U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return NullWrapper.ofNullable(mapper.apply(value));
        }
    }

    /**
     * 终端操作,当最终结果还是为null时,返回默认值other
     *
     * @param other
     * @return
     */
    public T orElse(T other) {
        return value != null ? value : other;
    }

    // ---------- 对Object方法重写,不用关注 -----------

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        if (!(obj instanceof NullWrapper)) {
            return false;
        }

        NullWrapper<?> other = (NullWrapper<?>) obj;
        return Objects.equals(value, other.value);
    }


    @Override
    public int hashCode() {
        return Objects.hashCode(value);
    }

    @Override
    public String toString() {
        return value != null
                ? String.format("Optional[%s]", value)
                : "Optional.empty";
    }
}

不知道大家有没有发现一个小心思:

对于连续为null的情况,其实并不会每次都重新new Wrapper(),而是直接调用empty()返回EMPTY,也就是空Wrapper复用。

所以,即使嵌套的都是null也不会每次都new Wrapper()哟~

Demo重构

/**
 * 假设这是A-Service的服务
 *
 * @param username
 * @return
 */
public static String getDepartmentNameOfUser(String username) {
    // 请求远程方法
    ResultTO<User> resultTO = getUserByName(username);
    // 包装为Wrapper
    NullWrapper<ResultTO<User>> resultTONullWrapper = NullWrapper.ofNullable(resultTO);
    // 链式调用
    return resultTONullWrapper
            .map(ResultTO::getData)
            .map(User::getDepartment) // 只有User不为null时,方法引用才会被调用,所以不用担心空指针,大胆的传入Lambda/方法引用吧!(其他同理)
            .map(Department::getName)
            .orElse("未知部门");
}

简写成:

/**
 * 假设这是A-Service的服务
 *
 * @param username
 * @return
 */
public static String getDepartmentNameOfUser(String username) {
    return NullWrapper.ofNullable(getUserByName(username))
            .map(ResultTO::getData)
            .map(User::getDepartment)
            .map(Department::getName)
            .orElse("未知部门");
}

是不是很简洁、很优雅呢~

Optional

其实上面的工具类就是模仿Java8 Optional写的,也已经演示了Optional最最核心的用法,所以这里不打算再对Optional进行长篇大论。

这里帮大家梳理一下Optional的API,标出几个重点且适合我们使用的方法:

被误解的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()处理

令人困惑的isPresent()

public static String getDepartmentNameOfUser(String username) {

    /**
     * 把isPresent()当做判断空指针的方法,又回归以前的if嵌套,意义不大
     * 个人觉得isPresent()不应该暴露出来,放在Optional内部使用更好
     */
    Optional<ResultTO<User>> result2 = Optional.ofNullable(getUserByName("test"));
    if (result2.isPresent()) {
        User user = result2.get().getData();
        if (user != null) {
            // ...继续if判断空指针
        }
    }
}

当然,上面只是我个人觉得不合适的用法(JPA返回值就是Optional,很多人也是直接使用isPresent判空)。实际使用时,可以根据情况灵活应对。比如,当前得到Optional后已经是最后一步了,那么可以直接这样:

// 筛选出最大折扣的优惠券。max()返回的是Optional类型,但由于后面不再做get操作,可以直接用orElse(null)返回
Coupon maxDiscountCoupon = coupons.stream().max(Comparator.comparingDouble(Coupon::getDiscount)).orElse(null);
return maxDiscountCoupon;

如果真的需要判断是否存在并做进一步操作,对于单级判断可以用ifPresent()代替isPresent(),对于多级判断可以先用map()在用ifPresent()或orElse()等。

另外,由于Optional并没有实现Serializable接口,所以不推荐直接作为pojo字段使用。虽然可以作为内部方法的返回值,免去调用时手动包装,但这意味着强制调用者使用Optional,可能影响办公室和谐。应该把Optional当做一个简单的工具类,仿佛是你身边的同事封装的,不要对它抱有过分的期待,它的使命就一个:简化冗余的空指针探测。

推荐使用场景

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

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;
    }
}

在很长一段时间里,我都不知道filter()方法存在的意义,因为Optional本身可以保证不发生空指针异常,那么我随便map()即可,反正最后orElse()兜底。但通过上面的案例,你应该也明白了:map()虽然好用,但也存在局限性。

最后,由于Optional不如Stream那么具有革命性,所以实际工作中没那么普及,甚至让不了解Optional的同事感到困惑。经过这篇文章的学习,相信你已经爱上它,可以尝试在自己团队中推广~

差点忘了,Stream API其实也使用了Optional:

private static void getMax() {
    List<Department> list = new ArrayList<>();
    list.add(new Department("总裁办", 1));
    list.add(new Department("财务部", 2));
    list.add(new Department("安保部", 3));

    Optional<Department> max = list.stream().max(Comparator.comparingInt(Department::getCode));
    Optional<Department> min = list.stream().min(Comparator.comparingInt(Department::getCode));
    Optional<Department> first = list.stream().findFirst();
    Optional<Department> any = list.parallelStream().findAny();
}

Optional并不是消除空指针判断,而是把判断逻辑塞进了诸如map()等方法中,再配合链式调用,让代码更简洁精炼。

Java8 Optional的不足

如果你从未用过Optional,那么这一小节要讲的东西你可能无法感同身受。这里举一个场景:

public List<UserTO> listUser(List<Long> userIds) {
    return Optional.ofNullable(userDao.listUser(userIds))
        .map(...) // 我想在这一步把所有UserDO转为UserTO
        .orElse(Collections.emptyList());
}

但实际上Optional的map()是没有“遍历操作”的含义的。什么意思呢?对于:

userList.stream()

.map("把UserDO转为UserTO")

.collec(Collectors.toList())

这里的map其实是在一个循环里进行的,对于userList中的每个UserDO都会执行“UserDO转UserTO”的操作。但Optional的map()只能对userList整体进行转换,而不是对userList中的每一个UserDO(好好理解一下)。

在日常开发过程中,遇到这种情况我通常只能这样写:

public List<UserTO> listUser(List<Long> userIds) {
    return Optional.ofNullable(userDao.listUser(userIds))
        .map(userList -> {
             // 内嵌一个stream
        	 return userList.stream().map(userDO -> {
             	UserTO userTO = new UserTO();
        		// 拷贝UserDO属性到UserTO
            	return userTO;
             }).collect(Collectors.toList());
        })
        .orElse(Collections.emptyList());
}

倒不是说看起来不够优雅啥的,只是如果把代码思路比作水流的话,每次用Optional处理List类型的数据并且需要做TO转换时,总觉得思想的水流到这里就遇到了一块大石头,总不免要荡起水花。

据说JDK9对Optional做了扩展,大家有兴趣可以了解下。

总结

我的体会是,Optional并不难,就是一个工具类,没几行代码,并且不存在丝毫的继承关系。难的是很多人不知道什么时候该用Optional、怎么用Optional。

如果大家去百度或知乎搜索“Optional”,会发现许多文章写得并不好,它们举的例子非常空洞,我甚至觉得作者其实根本没用过Optional,就是为了介绍每一个方法而强行弄出一个例子,最终我们虽然看完了整篇文章,却无法灵活应用Optional。

还是要靠大家自己实际开发时多体会体会。

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

进群,大家一起学习,一起进步,一起对抗互联网寒冬

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值