一、Optional类概述
1.1 Optional类介绍
Optional 类是 Java 8 才引入的,Optional 是个容器,它可以保存类型 T 的值,或者仅仅保存 null。Optional 提供了很多方法,这样我们就不用显式进行空值检测。Optional 类的引入很好的解决空指针异常。通过Optional提供的方法,如map、orElse等,可以更优雅地处理可能为空的对象,避免显式的null检查,使代码更简洁。文章介绍了Optional的创建、获取值的方法以及注意事项,如避免基础类型的Optional和合理使用orElse方法。
1.2 Java8之前的空指针异常判断
Java 在使用对象过程中,访问任何方法或属性都可能导致 NullPointerException:
例如我们通过以下方法,获取存在 student 对象中的 Age 值。
public String getIsocode (Student student){
return student.getAge();
}
在这样的示例中,如果我们想要避免由 student 或 student.age 为空而导致的空指针问题,我们就需要采用防御式检查减少 NullPointerException(在访问每一个值之前对其进行明确地检查):
public String getIsocode (Student student){
if (null == student) {
// doSomething
return "Unknown";
}
if (null = student.getAge()) {
// doSomething
return "Unknown";
}
return student.getAge();
}
然而,这种方案并不是很理想,因为为此会多出多个不同的退出点(return),使得代码维护变得艰难,之后每个可能的 null 检查都会新增一个退出点。
为了简化这个过程,我们来看看用 Optional 类是怎么做的。
1.3 Java8之后Optional的使用
当需要判断的量多时,此时的这些判断语句可能会导致代码臃肿冗余,为此 Java8 特意推出了 Optional 类来帮助我们去处理空指针异常。
下面是 Optional 的一些基本用法:
@Data
public class Student {
private Integer age;
}
---
public class Test {
public static void main(String[] args) {
// 假设 student 这个对象从数据库中查出的
Student student = getStudent();
// 创建一个可接受 null 的 Optiona l类
Optional<Student> optional = Optional.ofNullable(student);
// 用法1:获取 student 对象中的某个值,如果不存在的话则取默认值(不具有短路作用)
Integer a1 = optional.map(Student::getAge).orElse(20));
// 用法2:获取 student 对象中的某个值,如果不存在的话则取默认值(具有短路作用,因为是懒加载)
Integer a2 = optional.map(Student::getAge).orElseGet(() -> Integer.MAX_VALUE);
// 用法3:判断对象是否存在,不存在则抛出异常
optional.orElseThrow(() -> new RuntimeException("student不存在!"));
// 用法4:判断对象是否存在,存在的话对对象进行操作,例如给对象赋初始值
optional.ifPresent(o -> o.setAge(18));
// 用法5:对象存在时,且年龄满足一定条件容器才会继续保存这对象,否则将会剔除
optional.filter(o -> o.getAge() > 10);
}
}
二、Optional类使用
2.1 Optional类常用方法总结
方法 | 描述 |
---|---|
empty | 返回一个空的 Optional 实例 |
filter | 如果值存在并且满足提供的谓词,就返回包含该值的 Optional 对象;否则返回一个空的 Optional 对象 |
flatMap | 如果值存在,就对该值执行提供的 mapping 函数调用,返回一个 Optional 类型的值,否则就返回一个空的 Optional 对象 |
get | 如果该值存在,将该值用 Optional 封装返回,否则抛出一个 NoSuchElementException 异常 |
ifPresent | 如果值存在,就执行使用该值的方法调用,否则什么也不做 |
isPresent | 如果值存在就返回 true,否则返回 false |
map | 如果值存在,就对该值执行提供的mapping 函数调用 |
of | 将指定值用 Optional 封装之后返回,如果该值为 null,则抛出一个 NullPointerException 异常 |
ofNullable | 将指定值用 Optional 封装之后返回,如果该值为 null,则返回一个空的 Optional 对象 |
orElse | 如果有值则将其返回,否则返回一个默认值 |
orElseGet | 如果有值则将其返回,否则返回一个由指定的 Supplier 接口生成的值 |
orElseThrow | 如果有值则将其返回,否则抛出一个由指定的 Supplier 接口生成的异常 |
2.2 Optional对象创建
1、Optional.empty()方法
使用 Optional.empty() 方法声明一个空的 Optional:
// 通过静态工厂方法 Optional.empty(),创建一个空的 Optional 对象
Optional<Student> optStudent = Optional.empty();
2、Optional.of(T t)方法
使用 Optional.of(T t) 方法创建一个包含非空值的 Optional 对象 (不推荐):
// 静态工厂方法 Optional.of(T t),依据一个非空值创建一个 Optional 对象
Optional<Student> optStudent = Optional.of(student);
如果 student 为 null,这段代码会立即抛出一个 NullPointerException,而不是等到访问 student 的属性值时才返回一个错误。
3、Optional.ofNullable(T t)方法
使用 Optional.ofNullable(T t) 方法创建一个包含可能为空的值的 Optional 对象 (推荐):
// 用静态工厂方法 Optional.ofNullable(T t),你可以创建一个允许 null 值的 Optional 对象
Optional<Student> optStudent = Optional.ofNullable(student);
2.3 Optional对象获取
1、get()方法
get() 方法,如果变量存在,它直接返回封装的变量值,否则就抛出一个 NoSuchElementException 异常,不推荐使用:
optional.map(Student::getAge).get()
2、orElse(T other)方法
orElse(T other) 方法,它允许你在 Optional 对象不包含值时提供一个默认值:
optional.map(Student::getAge).orElse(20));
3、orElseGet(Supplier<? extends T> other)方法
orElseGet(Supplier<? extends T> other) 方法,它是 orElse 方法的延迟调用版,Supplier 方法只有在 Optional 对象不含值时才执行调用(懒加载):
optional.map(Student::getAge).orElseGet(() -> Integer.MAX_VALUE);
4、orElseThrow(Supplier<? extends X> exceptionSupplier)方法
orElseThrow(Supplier<? extends X> exceptionSupplier) 方法,它和 get 方法非常类似,它们遭遇 Optional 对象为空时都会抛出一个异常,但是使用 orElseThrow 可以定制希望抛出的异常类型:
optional.orElseThrow(() -> new RuntimeException("student不存在!"));
5、ifPresent(Consumer<? super T> consumer)方法
ifPresent(Consumer<? super T> consumer) 方法,它让能在变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作:
optional.ifPresent(o -> o.setAge(18));
2.4 Optional对象中值的提取和转换
1、map()方法
map() 方法,如果值存在,就对该值执行提供的 mapping 函数调用,如果值不存在,则返回一个空的 Optional 对象。
引入 Optional 以前:
String name = null;
if(insurance != null){
name = insurance.getName();
}
引入 Optional 以后:
Optional<String> name = Optional.ofNullable(insurance).map(Insurance::getName);
Optional 的 map 方法和 Java 8 中 Stream 的 map 方法相差无几。
2、flatMap()方法
flatMap() 方法,对于嵌套式的 Optiona 结构,我们应该使用 flatMap 方法,将两层的 Optional 合并成一个。
我们试着重构以下代码:
public String getCarInsuranceName(Person person) {
return person.getCar().getInsurance().getName();
}
由于我们刚刚学习了如何使用 map,我们的第一反应可能是我们可以利用 map 重写之前的代码:
Optional<Person> optPerson = Optional.of(person);
Optional<String> name =
optPerson.map(Person::getCar)
.map(Car::getInsurance)
.map(Insurance::getName);
不幸的是,这段代码无法通过编译。为什么呢? optPerson 是 Optional 类型的 变量, 调用 map 方法应该没有问题。但 getCar 返回的是一个 Optional 类型的对象,这意味着 map 操作的结果是一个 Optional<Optional> 类型的对象。因此,它对 getInsurance 的调用是非法的。
下面应用 map 和 flatMap 对上述示例进行重写:
public String getCarInsuranceName(Optional<Person> person) {
return person.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown"); // 如果Optional的结果 值为空设置默认值
}
2.5 Optional对象其他方法
1、isPresent()方法
可以使用 isPresent() 方法检查 Optional 对象是否包含非空值,例如:
Optional<String> optional = Optional.of("Hello World");
if (optional.isPresent()) {
System.out.println(optional.get());
}
2、filter()方法
filter() 方法接受一个谓词作为参数。如果 Optional 对象的值存在,并且它符合谓词的条件,filter 方法就返回其值,否则它就返回一个空的 Optional 对象。
比如,你可能需要检查保险公司的名称是否为 “Cambridge-Insurance”。
Insurance insurance = ...;
if(insurance != null && "CambridgeInsurance".equals(insurance.getName())){
System.out.println("ok");
}
使用 Optional 对象的 filter 方法,这段代码可以重构如下:
Optional<Insurance> optInsurance = ...;
optInsurance.filter(insurance -> "CambridgeInsurance".equals(insurance.getName()))
.ifPresent(x -> System.out.println("ok"));
三、Optional注意事项
3.1 Optional的序列化问题
由于 Optional 类设计时就没特别考虑将其作为类的字段使用,所以它也并未实现 Serializable 接口。由于这个原因,如果你的应用使用了某些要求序列化的库或者框架,在域模型中使用Optional,有可能引发应用程序故障。
然而,我们相信,通过前面的介绍,我们已经看到用 Optional 声明域模型中的某些类型是个不错的主意,尤其是你需要遍历有可能全部或部分为空,或者可能不存在的对象时。如果你一定要实现序列化的域模型,作为替代方案, 我们建议你像下面这个例子那样,提供一个能访问声明为 Optional、变量值可能缺失的接口,代码清单如下:
public class Person {
private Car car;
public Optional<Car> getCarAsOptional() {
return Optional.ofNullable(car);
}
}
3.2 避免使用基础类型的 Optional 对象
Optional 提供了的一些基础类型 —— OptionalInt、OptionalLong 以及 OptionalDouble ,但不推荐大家使用基础类型的 Optional,因为基础类型的 Optional 不支持 map、 flatMap 以及 filter 方法,而这些却是 Optional 类常用的方法。可以使用 Optional, Optional, Optional 等替代。
3.3 orElse方法的使用
orElse 中调用的方法一直都会被执行,orElseGet 方法只有在 Optional 对象不含值时才会被调用,所以使用 orElse 方法时需要谨慎, 以免误执行某些不被预期的操作。此种情况下,可使用 orElseGet 方法代替它。