一、Optional定义
A container object which may or may not contain a non-null value。这是Optional类中官方的定义,从此可以看出,optinal相当于一个容器,它所包含的内容可以为空也可以不为空。java8推出Optional类是受到Google Guava的启发,主要用来优雅的处理空指针异常,让我们的代码结构更加清晰,避免因为频繁的空判断,造成代码层级变深,难以阅读。
二、Optional使用场景
【注意】:以下使用为实际使用场景,不包含对Optional中各方法的介绍,请先自行阅读官方对各方法的介绍
1.使用Optional优化多层调用 需求:有三个实体,User1、User2、User3,User2为User1一个属性,User3为User2一个属性,User3包含两个属性分别为年龄、姓名,定义一个方法入参为User1,功能是当年龄不为空时输出年龄,否则抛出异常。 不使用Optional时的三种常见实现方式:
public void getUserName(User1 user) { // 第一种方式 if (user != null && user.getUser2() != null && user.getUser2().getUser3() != null && StringUtils.isNotBlank(user.getUser2().getUser3().getName())) { log.info("一般方式获取用户姓名:{}", user.getUser2().getUser3().getName()); } else { throw new RuntimeException("获取用户姓名失败"); } // 第二种方式 // if (user != null) { // User2 user2 = user.getUser2(); // if (user2 != null) { // User3 user3 = user2.getUser3(); // if (user3 != null && StringUtils.isNotBlank(user3.getName())) { // log.info("一般方式获取用户姓名:{}", user3.getName()); // } else { // throw new RuntimeException("获取用户姓名失败"); // } // } else { // throw new RuntimeException("获取用户姓名失败"); // } // } else { // throw new RuntimeException("获取用户姓名失败"); // } // 第三种方式 // if (user == null){ // throw new RuntimeException("获取用户姓名失败"); // } // if (user.getUser2() == null){ // throw new RuntimeException("获取用户姓名失败"); // } // if (user.getUser2().getUser3() == null){ // throw new RuntimeException("获取用户姓名失败"); // } // if (StringUtils.isBlank(user.getUser2().getUser3().getName())){ // throw new RuntimeException("获取用户姓名失败"); // } // log.info("一般方式获取用户姓名:{}", user.getUser2().getUser3().getName()); }
使用Optional的实现方式:
public void getUserNameOptional(User1 user) { String s = Optional.ofNullable(user) .map(User1::getUser2) .map(User2::getUser3) .map(User3::getName) .filter(StringUtils::isNotBlank) .orElseThrow(() -> new RuntimeException("获取用户姓名失败")); log.info("optinal获取用户姓名:{}", s); } 2.使用stream加optional对集合进行过滤
public void getUserNameNotNull(List<User1> list) { // 常规 List<User1> collect1 = list.stream().filter(user1 -> !Objects.isNull(user1) && !Objects.isNull(user1.getUser2()) && !Objects.isNull(user1.getUser2().getUser3()) && StringUtils.isNotBlank(user1.getUser2().getUser3().getName())).collect(Collectors.toList()); log.info("一般方式:{}", JSON.toJSONString(collect1)); List<User1> collect = list.stream().filter(user -> Optional.ofNullable(user) .map(User1::getUser2) .map(User2::getUser3) .map(User3::getName) .filter(StringUtils::isNotBlank).isPresent()) .collect(Collectors.toList()); log.info("optional方式:{}", JSON.toJSONString(collect)); } 3.值不为空时执行特定操作
public void writeLog(User1 user) { Optional.ofNullable(user).ifPresent(u -> log.info(JSON.toJSONString(u))); } 4.使用Optional增强mybatis-plus,对对象类型查询结果进行包装 第一种方式:自己创建一个父mapper,继承BaseMapper并使用Optional对BaseMapper中提供的各通用查询方法进行封装,其他mapper均继承自定义的父mapper。
public User3 queryData(String name) { // 将mapper中继承BaseMapper替换为继承自己封装后的 Optional<User3> optionalUser1 = user3Mapper.getOne(new QueryWrapper<User3>().lambda().eq(User3::getName, name)); return optionalUser1.orElseThrow(() -> new RuntimeException("未获取到用户信息")).setAge(20); }
第二种方式:使用Optional对每一条sql的查询结果进行封装,高版本的mybatis-plus支持结果使用Optional包装
public User3 queryData(String name) { Optional<User3> optionalUser2 = userMapper.getInfo(name); return optionalUser2.orElseThrow(() -> new RuntimeException("未获取到用户信息")).setAge(20); }
5.使用Optional + stream流,对集合类型的查询结果进行包装、处理 一般方式:
public User3 queryDataList(String name) { List<User3> list = user3Mapper.selectList(new QueryWrapper<User3>().lambda().eq(User3::getName, name)); if (!CollectionUtils.isEmpty(list)) { list.stream().findFirst().orElseThrow(() -> new RuntimeException("未获取到用户信息")).setAge(20); } throw new RuntimeException("未获取到用户信息"); }
使用Optional包装:
public User3 queryDataList(String name) { Optional<List<User3>> infoList = user3Mapper.list(new QueryWrapper<User3>().lambda().eq(User3::getName, name)); return infoList.orElseThrow(() -> new RuntimeException("未获取到用户信息")) .stream().findFirst().orElseThrow(() -> new RuntimeException("未获取到用户信息")).setAge(20); }
6.Optional在配置变量注入时的使用
例:有一个配置项,如果配置了值则使用配置值,没有配置则需要从根据上下文的变量进行计算
@Value("${optional.properties:}") private Optional<Integer> value;
public Integer getPropertiesValue() { int a = 1; int b = 2; return value.orElseGet(() -> a * b); }
三、需要避免的使用方式
1.不要使用使用Optional对确认不为空的值进行包装 一般来说,使用Optional包装,代表我们不确定他的内容是否一定存在,也就是说该值可能为空,后续调用方在使用此值时,根据自己的业务需要考虑是否需要进行空判断。而当某一个值必然不为空时,如果继续使用Optional进行包装,使用方可能误以为值可能为空依然进行空检测,那么可能增加使用方的工作量。 2.避免使用Optional.isPresent()来检查实例是否存在 isPresent()方法只会返回质是否为空的布尔值,不支持链式调用其他方案,如果单纯为了使用他进行空检测,而包装一层Optional那是没有意义的不如直接使用!= null直接判断 3.避免使用Optional.get()方式来获取实例对象 该方法在获取值之前不会进行值为空的判断,可能造成空指针异常,这与我们使用Optional时的初衷希望优雅的处理空指针背道而驰,建议使用其他两个获取值的方式获取值。 4.不要将Optional对象初始化为null 永远不要将一个Optional对象初始化为null,这样会造成使用时还需要额外对这个容器进行判空操作,不仅无法优化我们的代码,甚至会让我们的代码更加复杂。 5.避免使用Optional作为类或者实例的属性,Optional没有实现Serializable接口,如果进行序列化会产生异常