Java 8 之前的问题
在Java 8 之前,编写一个在特定条件下无法返回值的方法时,有两种方法:一是抛出异常,二是返回null (我们假设方法返回的是对象引用类型),这两种方法都有缺点,抛出异常会捕获整个堆栈轨迹,会有一定的性能开销,返回null 没这缺点,但是会强迫客户端代码有判断值为null 处理代码,如果忽略判断,在运行时可能抛出 NullPointerException
异常。
Optional 简介
java 8 引入了类 Optional , 一个容器类,用 final 修饰,它可以存放一个非 null 的 T 引用,或者什么也不存放。不包含任何内容的optional 成为空(empty),非空的optional 成为存在(present) ,它还提供了一系列便利的方法,后面一一介绍。
Optional 用法
有个方法,根据ID 从数据获取信息
Employee findEmployee(String id)
调用此方法,传参1234,但是在数据库总不存在,会返回null, 如下代码会抛出 NullPointerException
Employee employee = findEmployee("1234");
System.out.println("Employee's Name = " + employee.getName());
我们用 Optional 来解决,防止抛出 NullPointerException ,方法定义如下
Optional < Employee > findEmployee(String id);
Optional < Employee > optional = findEmployee("1234");
optional.ifPresent(employee -> {
System.out.println("Employee name is " + employee.getName());
})
上面代码的意思是当返回值存在时才打印信息。上面举了一个很普遍的例子来说明optional 的用法,下面详细介绍具体用法。
- 创建Optional对象
当数据库找不到对应信息,需要返回一个空Optional 时,如下方式赋值
Optional < Employee > employee = Optional.empty();
返回非 null 对象时
Optional < Employee > optional = Optional.of(employee);
返回可 null 对象时,允许 employee 为 null, 但是 optional 的 value 实际是 empty
Optional < Employee > optional = Optional.ofNullable(employee);
Optional.ofNullable 的源代码如下
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
-
判断Optional 对象的值是否存在
isPresent() 方法来判断值是否存在,返回值为 boolean 类型,用法如下
if (optional.isPresent()) { System.out.println("Value - " + optional.get()); } else { System.out.println("Optional is empty"); }
如果想在有值情况下处理一些逻辑,但不想用if 语句判断,可以用方法 ifPresent(Consumer<? super T> consumer) ,接收一个 Consumer 函数
optional.ifPresent(value -> { System.out.println("Value present - " + value); });
-
获取 Optional 对象的值
get() 方法可以获取值,但是当值为null 时,抛出 NoSuchElementException
orElse(T) 方法,表示当值为 null 是,指定一个值返回
orElseGet(Supplier<? extends T> other) ,表示当值为 null 时,执行一个Supplier 函数,示例如下
java Employee finalEmployee = optional.orElseGet(() -> { return new Employee("0", "Unknown Employee"); });
-
当值不存在时抛出异常
@GetMapping("/employees/{id}")
public User getEmployee(@PathVariable("id") String id) {
return employeeRepository.findById(id).orElseThrow(() ->
new ResourceNotFoundException("Employee not found with id " + id));
}
- 过滤值对象的属性
获取一个对象时,我们经常来判断对象的属性值,满足一定条件时执行一些其他业务逻辑,一般实现代码,
if(employee != null && employee.getGender().equalsIgnoreCase("MALE")) {
// calling the function
}
使用optional 的 filter 方法实现同样逻辑:
optional.filter(user ->
employee.getGender().equalsIgnoreCase("MALE"))
.ifPresent(() -> { });
- 用map 方法抽取转换值
类 Employee 有个获取地址的方法
Address getAddress(){
return this.address;
}
有个场景:判断员工属于某个国家时,执行一些业务逻辑,一般代码实现
if (employee != null) {
Address address = employee.getAddress();
if (address != null && address.getCountry().equalsIgnoreCase("USA")) {
System.out.println("Employee belongs to USA");
}
}
利用map方法实现版本
userOptional.map(Employee::getAddress)
.filter(address -> address.getCountry().equalsIgnoreCase("USA")).ifPresent(() -> {
System.out.println("Employee belongs to USA");
});
- 用 flatMap 方法抽取并转换值
如果第6步的例子 getAddress 方法返回值是 Optional < Address >,那 userOptional.map(Employee::getAddress)
的返回值是 Optional< Optional < Address > >
, 此时出现了嵌套的 Optional , 可以用方法 flatMap 来解决,
userOptional.flatMap(Employee::getAddress)
.filter(address -> address.getCountry().equalsIgnoreCase("USA")).ifPresent(() -> {
System.out.println("Employee belongs to USA");
});
总结
使用 java 8 Optional 的优点
- 有效避免 NullPointerException
- 不需要写代码判断值为Null
- 利用Optional 提供的方法可以省去一些模板代码
- 可以设计出更加简洁优雅的API
注意点
- 永远不要通过Optional 返回 Null , 这违背了Optional 设计的本意
- 容器类包括集合、映射、Stream、数组和 Optional, 不应该被包装在Optional 中,直接返回空的容器类,更容易让客户端代码判断
- 不要用 Optional 类作为集合、数组、键和值的元素,这会给值或键存在的判断增加复杂度
- 尽量不要将Optional 用作返回值意外的任何用途