一、什么是Optional
在Java中什么异常最容易出现,那肯定是NullPointerException,空指针就像一个定时炸弹,总给我们带来些麻烦,在开发过程中都会碰到需要判断Null值以防止空指针的情况,以往的方式要么是抛异常,要么是if{}else{},直到Optional的出现,你可以更优雅的解决NPE问题。
首先我们来看一下 Optional的作者 Brian Goetz 对这个 API 的说明:
Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent “no result”, and using null for such was overwhelmingly likely to cause errors.
翻译过来大致意思:Optional提供了一个有限的机制让类库方法返回值清晰的表达有与没有值,避免很多时候 null 造成的错误
所以尝试在Java8+中使用工具类Optional,并在特定场景中简化代码逻辑。
二、Optional中有哪些常用方法
静态方法
- Optional.of(T value):为指定的值创建一个指定非 null 值的 Optional,需要注意的是传入的参数不能为 null,否则抛出 NullPointerException。
- Optional.ofNullable():为指定的值创建一个 Optional 对象,如果指定的参数为 null,不抛出异常,直接则返回一个空的 Optional 对象。
对象方法
- isPresent():判断optional是否为空,如果空则返回false,否则返回true
- get():如果 Optional 有值则将其返回,否则抛出 NoSuchElementException 异常
- ifPresent(Consumer c):如果optional不为空,则将optional中的对象传给Comsumer函数orElse(T other):如果optional不为空,则返回optional中的对象;如果为null,则返回 other 这个默认值
- orElseGet(Supplier other):如果optional不为空,则返回optional中的对象;如果为null,则使用Supplier函数生成默认值other
- orElseThrow(Supplier exception):如果optional不为空,则返回optional中的对象;如果为null,则抛出Supplier函数生成的异常
- filter(Predicate p):如果optional不为空,则执行断言函数p,如果p的结果为true,则返回原本的optional,否则返回空的optional
- map(Function<T, U> mapper):如果optional不为空,则将optional中的对象 t 映射成另外一个对象 u,并将 u 存放到一个新的optional容器中。
- flatMap(Function< T,Optional< U >> mapper):跟上面一样,在optional不为空的情况下,将对象t映射成另外一个optional,区别:map会自动将u放到optional中,而flatMap则需要手动给u创建一个optional
三、Optional误用场景
1. 直接使用 isPresent() 进行 if 检查
这么写和直接判断空没啥区别,反而增加了麻烦
public class User{private String name;private Integer age;public String getName() {return name;}public void setName(String name) {this.name = name;}
}
// 错误使用OptionalUser u=null;Optional<User> userOpt=Optional.ofNullable(u);if(userOpt.isPresent()){String name=u.getName();System.out.println(name);}// 直接判断if(u !=null){String name=u.getName();System.out.println(name);}
可以接受的写法:建议使用 ifPresent(Consumer<? super T> consumer),判断Optional中是否有值,有值则执行 consumer,否则什么都不干,不建议直接使用isPresent() 进行 if 检查进行判断
userOpt.ifPresent(user -> System.out.println(user.getName()));
其实isPresent() 更建议用在流处理的结尾,用于判断是否符合条件
list.stream().filer(x -> Objects.equals(x,param)).findFirst().isPresent()
2. 在方法参数中使用 Optional或者在POJO中使用
Optional本身并没有实现序列化,现有的 JSON 序列化框架大多数也没有对此提供支持的。
public class User {private String name;private Integer age;// 不建议private Optional<String> address;
}
3. 直接使用 Optional.get
Optional不会帮你做任何的空判断或者异常处理,如果直接在代码中使用 Optional.get()和不做任何空判断一样,十分危险,当没有值时会抛出一个NoSuchElementException异常
User u=null;
Optional<User> userOpt=Optional.ofNullable(u);
// 不建议
System.out.println(userOpt.get().getName());
得到得结果是:
获取 value 有三种方式:get() 、orElse()、 orElseGet()
get()不能直接使用,需要结合判空使用。这和!=null其实没多大区别,只是在表达和抽象上有所改善。
orElse() 和ElseGet() 区别
性能问题:无论如何都会执行括号中的内容, orElseGet()只在主体 value 是空时执行orElseGet()需要构建一个Supplier
使用不当,再次空指针,当orElse的参数是间接计算得来的时候。虽然这种说法有点牵强(因为并不是orElse导致了空指针异常),但是使用orElseGet确实可以避免这种情况
class User {// 中文名private String chineseName;// 英文名private EnglishName englishName;
}class EnglishName {// 全名private String fullName;// 简写private String shortName;
}
假如我们现在有User类,用户注册账号时,需要提供自己的中文名或英文名,或都提供,我们抽象出一个EnglishName类,它包含英文名的全名和简写(因为有的英文名确实太长了)。现在,我们希望有一个User#getName()方法,它可以像下面这样实现:当用户只提供了中文名时,此时englishName属性是null,但是在orElse中,englishName.getShortName()总是会执行。而在getName2()中,这个风险却没有。
class User {// ... 之前的内容public String getName1() {return Optional.ofNullable(chineseName).orElse(englishName.getShortName());}public String getName2() {return Optional.ofNullable(chineseName).orElseGet(() -> englishName.getShortName());}
}
如果只是简单的返回一个静态资源、字符串等等,直接返回静态资源使用orElse()即可。
4. 使用在注入的属性中
// 一般不建议
public class CommonService {private Optional<UserService> userService;
}
四、Optional正确使用姿势
1.将对象某个属性赋值给其他实体对象
// 调用服务层查询数据库获得目标对象
ProdSku prodSku = prodSKUService.getFirstProdSku(p.getProdId());
if (prodSku != null) {prodAPP.setPrice(prodSku.getPrice());
}
// 使用Optional 和函数式编程,一次完成
Optional.ofNullable(prodSku).ifPresent(p -> prodAPP.setPrice(p.getPrice()));
2.当取不到值时,返回你指定的 default
如果指定值固定的,明确的建议用orElse(),如果指定值是不明确的或者是需要大量计算得出的建议用orElseGet()
2.1判断一个集合大小,但又怕这个集合是null
List<User> list=null;
Optional<List<User>> optional=Optional.ofNullable(list);
int size = optional.map(List::size).orElse(0);
// 输出0
System.out.println(size);
// 直接调用集合的size方法就有可能造成 NullPointerException 空指针异常
System.out.println(list.size());
2.2实体类get方法初始化赋值
// 当传给前端同学一个字段时候,并不希望这个值出现null,但数据库里确实就是null的情况
public BigDecimal getAccount() {if (account == null) {account = BigDecimal.ZERO;}return account;
}
// 代替:
public BigDecimal getAccount() {return Optional.ofNullable(account).orElse(BigDecimal.ZERO) ;
}
2.3 类型之间转换,当没有值时候就返回一个默认值
String price=null;
Double c=Optional.ofNullable(price).map(a->Double.valueOf(a)).orElse(0.00);
System.out.println(c);
// 直接转换必定空指针错误
Double b=Double.valueOf(price);
2.4获取对象对应的汉字信息
如果是null就直接返回不获取,不论是a是null,还是对a运算是null,都能走到orElse 里面去的
Optional<Integer> optional=Optional.ofNullable(super.getAduitStatus());return optional.map(a->AduitStatusEnum.name(String.valueOf(a))).orElse(null);
3.多层属性获取(获取一个多层嵌套实体的值,收益最明显的一个例子)
//+++++++++++++++++++++++模拟这个要取的对象从其他接口传过来是不是null++++++++++++++++++++++++++++++++++++InetSocketAddress inetSocketAddress = new InetSocketAddress(2);String re="小明取不到值";if(inetSocketAddress !=null){if(inetSocketAddress.getAddress() !=null){if(inetSocketAddress.getAddress().getHostName() !=null){String name2 = inetSocketAddress.getAddress().getHostName().toUpperCase();if(StringUtils.isNotBlank(name2) ){re=name2;}}}}System.out.println(re);Optional<InetSocketAddress> optional=Optional.ofNullable(inetSocketAddress);String op= optional.map(a ->a.getAddress()).map(b ->b.getHostName()).map(c->c.toUpperCase()).orElse("小王也取不到值");System.out.println(op);//+++++++++++++++++++++++模拟这个要取的对象从其他接口传过来是null++++++++++++++++++++++++++++++++++++String re2="小明取不到值";InetSocketAddress inetSocketAddress2 = null;if(inetSocketAddress2 !=null){if(inetSocketAddress2.getAddress() !=null){if(inetSocketAddress2.getAddress().getHostName() !=null){String name2 = inetSocketAddress2.getAddress().getHostName().toUpperCase();if(name2 ==null){re2=name2;}}}}System.out.println(re2);Optional<InetSocketAddress> optional2=Optional.ofNullable(inetSocketAddress2);String op2= optional2.map(a ->a.getAddress()).map(b ->b.getHostName()).map(c->c.toUpperCase()).orElse("小王也取不到值");System.out.println(op2);
运行结果如下图所示
4.阻塞性业务场景推荐使用
BigDecimal stock = Optional.ofNullable(lcStockObj).map(a -> a.getAssetNum()).orElseThrow(() -> new RuntimeException("库存数据不存在,出入库失败"));
参考链接:https://blog.csdn.net/rhythm_1/article/details/121716010