Optional<T>类是一个容器类,代表一个值存在不存在。Optional<T>用于避免和 null 检查相关的 bug。
创建Optional
-
Optional.empty()—— 创建一个空的Optional对象Optional<String> optStr = Optional.empty(); -
Optional.of()—— 依据非空值创建一个Optional对象。如果试图传入一个null值,会马上抛出一个NullPointerException。Optional<String> optStr = Optional.of(str); -
Optional.ofNullable()—— 创建一个允许为null值的Optional对象。Optional<String> optStr = Optional.ofNullable(str);
map 和 flatMap
-
map操作 —— 当Optional的值不为空时,将Optional的值转换为对应的值,并将其封装成Optional对象返回。如果原本的Optional对象的值为空,则返回一个空的Optional对象。public <U> Optional<U> map(Function<? super T, ? extends U> mapper) { Objects.requireNonNull(mapper); if (!isPresent()) { return empty(); } else { //拿到新值后,再封装成Optional类型 return Optional.ofNullable(mapper.apply(value)); } } -
flatMap操作 —— 当Optional的值不为空时,将Optional的值转换为对应的值,新的值必须为Optional类型,并将其直接返回。如果原本的Optional对象的值为空,则返回一个空的Optional对象。public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) { Objects.requireNonNull(mapper); if (!isPresent()) { return empty(); } else { @SuppressWarnings("unchecked") //拿到新值后强转Optional类型,不进行封装 Optional<U> r = (Optional<U>) mapper.apply(value); return Objects.requireNonNull(r); } }
注:
Optional 的 map 和 flatMap如何选择?
map会将转换好的值进行一次Optional包装;flatMap会确保转换好的值为Optional对象,然后直接返回。使用 map 还是 flatMap 取决于 转换好的值 是否是Optional对象。
如果 转换好的值 不是Optional对象,使用map,对其进行再次包装,以便执行进一步Optional的操作。
如果 转换好的值 是 Optional 对象,使用flatMap,直接返回。
Optional的其他行为
isPresent方法:Optional包含值的时候返回true,否则返回false。ifPresent方法:当值存在时,使用该值执行给定的代码块,否则什么都不做。- 与Kotlin的安全调用运算符相似,只有值不为空时,具体方法才会被调用。
get方法: 如果值存在则将其返回,否则抛出一个NoSuchElement Exception异常。orElse方法:如果值存在则将其返回,否则返回一个默认值。filter方法:如果值存在,并且满足提供的谓词,就返回自身;否则返回一个空的Optional对象。orElseGet方法:如果有值则将其返回,否则返回一个由指定的Supplier接口生成的值。(Supplier方法只有在Optional不为空时才执行调用)orElseThrow方法:如果有值则将其返回,否则返回一个由指定的Supplier接口生成的异常。orElseThrow重载方法(无参):如果有值则将其返回,否则直接抛出NoSuchElementException。(Java 10)or方法:如果值存在则将其返回,否则返回由指定的Supplier接口生成的另一个Optional对象。(Java 9)ifPresentOrElse方法:如果值存在,则使用该值作为参数,执行指定的Consumer接口;如果该值不存在,则执行给定的Runnable,处理值为空的情况。(Java 9)
Optional与序列化
对于值可能缺失(即可能为null)的属性,可以将其使用Optional包裹,明确表示该属性的值可缺失,类似kotlin的可空类型,强制需要进行空检查。
public class Person{
//Car可能为null,使用Optional对其进行封装
private Optional<Car> car;
public Optional<Car> getCar() {return car; }
}
但由于Optional的设计初衷仅仅是要支持能返回Optional对象的语法,因此它没实现 Serializable 接口。如果使用某些要求序列化的库或框架,在域模型中使用 Optional,有可能引发程序故障。
如果一定要实现序列化的域模型,替代方案是:提供一个能访问声明为Optional、变量值可能缺失的接口:
public class Person{
//虽然知道car可空,但不提倡一开始就定义Optional<Car>类型。
private Car car;
public Optional<Car> getCarAsOptional(){
return Optional.ofNullable(car);
}
}
Optional 与 流
JAVA 9 引入了 Optional 的 stream() 方法,该方法可以把一个含值的 Optional 对象转换成由该值构成的Stream 对象,或者把一个空的 Optional 对象转换成等价的空Stream。该方法为处理由 Optional 构成的 Stream 提供极大的便利。
//Optional#stream源码
public Stream<T> stream() {
if (!isPresent()) {
return Stream.empty();
} else {
return Stream.of(value);
}
}
借助Optional,可以安全的对流元素进行转换、刷选的操作:
//依据前面序列化的要求,提供返回Optional封装属性的方法。
class Person{
private Car car;
public Optional<Car> getCarAsOptional() { return Optional.ofNullable(car); }
}
class Car{
private Insurance insurance;
public Optional<Insurance> getInsuranceAsOptional() { return Optional.ofNullable(insurance); }
}
class Insurance{
private String name;
public String getName() { return name; }
}
//接收一个Person列表
public Set<String> getCarInsuranceNames(List<Person> persons){
return persons.stream()
//将流转换为Stream<Optional<Car>>
.map(Person::getCarAsOptional)
//将流转换为Stream<Optional<Insurance>>
.map(optCar -> optCar.flatMap(Car::getInsuranceAsOptional))
//将流转换为Stream<Optional<String>>
.map(optIns -> optIns.map(Insurance::getName))
//该流转换成Stream<String>
//如果使用Java8的,也可以参考Optional#stream进行处理
.flatMap(Optional::stream)
//将结果收集成Set
.collect(Collectors.toSet());
}
在 JDK 1.8 的环境下,可模仿Optional#stream()写一个将Optional对象转换为Stream对象的静态方法,然后使用方法引用将替代Optional::stream即可。这里可以使用OptionalUtility::stream 替代 Optional::stream。
//OptionalUtility.java
public static <T> Stream<T> stream(Optional<T> optional) {
if (!optional.isPresent()) {
return Stream.empty();
} else {
return Stream.of(optional.get());
}
}
对于早已定义好,不提供一个能访问声明为Optional、变量值可能缺失的接口的域模型,可以手动将流的元素转换为Optional对象:
class Person{
private Car car;
public Car getCar() {return car;}
}
class Car{
private Insurance insurance;
public Insurance getInsurance() { return insurance;}
}
class Insurance{
private String name;
public String getName() { return name; }
}
persons.stream()
//对元素使用Optional包装
.map(Optional::ofNullable)
.map(optPer -> optPer.map(Person::getCar))
.map(optCar -> optCar.map(Car::getInsurance))
.map(optIns -> optIns.map(Insurance::getName))
//如果使用Java8的,也可以参考Optional#stream进行处理
//拆除Optional包装
.flatMap(Optional::stream)
.collect(Collectors.toSet());
异常与Optional
由于某些原因,函数无法返回某个值,除了返回null外,Java API 比较常见的替代做法是抛出一个异常(最典型的例子就是Interger.parseInt() )。我们可以使用空的 Optional 对象,对遭遇无法转换的String进行建模时返回的非法值进行建模,从而不需要再封装try/catch :
//OptionalUtility.java
public static Optional<Integer> stringToInt(String s){
try{
return Optional.ofNullable(Integer.parseInt(s));
}catch (Exception e){
return Optional.empty();
}
}
//
HashMap<String,String> map = new HashMap<>();
map.put("Java","8");
int version = Optional.ofNullable(map.get("Java"))
//使用OptionalUtility#stringToInt进行转换。
.flatMap(OptionalUtility::stringToInt)
.orElse(0);
以不解包的方式组合两个Optional对象
当需要操作两个Optional对象进行运算并返回包含结果的Optional对象时,或许你会想到以下实现方法:
public Insurance findInsurance(Person person,Car car){
//经过运算得到正确的Insurance
//此处模拟返回一个Insurance对象
return insurance;
}
public Optional<Insurance> nullSafeFindInsurance(Optional<Person> person,Optional<Car> car){
//判断两个Optional的值都存在
if (person.isPresent() && car.isPresent()){
//只有两个Optional的值都存在,才取出进行运算。
return Optional.ofNullable(findInsurance(person.get(),car.get()));
}else {
//否则返回一个空Optional对象。
return Optional.empty();
}
}
但这样的代码跟我们手动判空区别不大,但其实可以结合 flatMap 和 map,不解包的方式下实现:
public Optional<Insurance> nullSafeFindInsurance(Optional<Person> person,Optional<Car> car){
//如果person的值不存在,则直接返回一个空Optional对象。
//至于为什么用flatMap,因为car.map返回的是一个Optional对象
return person.flatMap(p ->
//如果map的值不存在,则直接返回一个空Optional对象。
car.map(c ->
//执行到这一步,说明两个Optional的值都存在,利用这两个值进行运算。
//运算出的Insurance值,map函数会对其使用Optional进行封装。
findInsurance(p,c))
);
}
简化if-else
当需要对某一个变量深度获取值时,往往会伴随多次判空,使用 Optional 能优化 if-else 结构:
public String getInsuranceByPerson(Person person){
return Optional.ofNullable(person)
.map(Person::getCar)
.map(Car::getInsurance)
.map(Insurance::getName)
.orElse("daqi");
}
总结
Optional类有时候并没有让代码变得更简洁,他的作用更多的是把 类型 转换为 对应的"可空类型" ,强制进行空检查(与Kotlin定义可空类型相似)。
总体来说,Optional可确保 流 进行map、filter操作时的空安全,以及对某个变量进行深度取值时简化if-else流程和确保空安全。当然也可以把普通的空检查转换为 Optional 后,再进行操作。
参考资料
Java8系列
若文章有错误或不对的地方,欢迎大佬们指出。