Optional 类是 Java 8 才引入的,Optional 是个容器,它可以保存类型 T 的值,或者仅仅保存 null。Optional 提供了很多方法,这样我们就不用显式进行空值检测。Optional 类的引入很好的解决空指针异常。 Java 8 引入 Optional 类,用来解决 NullPointerException。 Optional 代替
if…else
解决空指针问题,使代码更加简洁。
Optional 类是 Java 8 才引入的,Optional 是个容器,它可以保存类型 T 的值,或者仅仅保存 null。Optional 提供了很多方法,这样我们就不用显式进行空值检测。Optional 类的引入很好的解决空指针异常。
Java 8 引入 Optional 类,用来解决 NullPointerException。 Optional 代替 if…else
解决空指针问题,使代码更加简洁。
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 类是怎么做的。
当需要判断的量多时,此时的这些判断语句可能会导致代码臃肿冗余,为此 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);
}
}
方法 | 描述 |
---|---|
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 接口生成的异常 |
使用 Optional.empty()
方法声明一个空的 Optional:
// 通过静态工厂方法 Optional.empty(),创建一个空的 Optional 对象
Optional<Student> optStudent = Optional.empty();
使用 Optional.of(T t)
方法创建一个包含非空值的 Optional 对象 (不推荐):
// 静态工厂方法 Optional.of(T t),依据一个非空值创建一个 Optional 对象
Optional<Student> optStudent = Optional.of(student);
如果 student 为 null,这段代码会立即抛出一个 NullPointerException,而不是等到访问 student 的属性值时才返回一个错误。
使用 Optional.ofNullable(T t)
方法创建一个包含可能为空的值的 Optional 对象 (推荐):
// 用静态工厂方法 Optional.ofNullable(T t),你可以创建一个允许 null 值的 Optional 对象
Optional<Student> optStudent = Optional.ofNullable(student);
get()
方法,如果变量存在,它直接返回封装的变量值,否则就抛出一个 NoSuchElementException 异常,不推荐使用:
optional.map(Student::getAge).get()
orElse(T other)
方法,它允许你在 Optional 对象不包含值时提供一个默认值:
optional.map(Student::getAge).orElse(20));
orElseGet(Supplier<? extends T> other)
方法,它是 orElse 方法的延迟调用版,Supplier 方法只有在 Optional 对象不含值时才执行调用(懒加载):
optional.map(Student::getAge).orElseGet(() -> Integer.MAX_VALUE);
orElseThrow(Supplier<? extends X> exceptionSupplier)
方法,它和 get 方法非常类似,它们遭遇 Optional 对象为空时都会抛出一个异常,但是使用 orElseThrow 可以定制希望抛出的异常类型:
optional.orElseThrow(() -> new RuntimeException("student不存在!"));
ifPresent(Consumer<? super T> consumer)
方法,它让能在变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作:
optional.ifPresent(o -> o.setAge(18));
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 方法相差无几。
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<Person>
类型的 变量, 调用 map 方法应该没有问题。但 getCar 返回的是一个 Optional<Car>
类型的对象,这意味着 map 操作的结果是一个 Optional<Optional<Car>>
类型的对象。因此,它对 getInsurance 的调用是非法的。
下面应用 map 和 flatMap 对上述示例进行重写:
public String getCarInsuranceName(Optional<Person> person) {
return person.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown"); // 如果Optional的结果 值为空设置默认值
}
可以使用 isPresent()
方法检查 Optional 对象是否包含非空值,例如:
Optional<String> optional = Optional.of("Hello World");
if (optional.isPresent()) {
System.out.println(optional.get());
}
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"));
由于 Optiona l类设计时就没特别考虑将其作为类的字段使用,所以它也并未实现 Serializable 接口。由于这个原因,如果你的应用使用了某些要求序列化的库或者框架,在域模型中使用Optional,有可能引发应用程序故障。
然而,我们相信,通过前面的介绍,我们已经看到用 Optional 声明域模型中的某些类型是个不错的主意,尤其是你需要遍历有可能全部或部分为空,或者可能不存在的对象时。如果你一定要实现序列化的域模型,作为替代方案, 我们建议你像下面这个例子那样,提供一个能访问声明为 Optional、变量值可能缺失的接口,代码清单如下:
public class Person {
private Car car;
public Optional<Car> getCarAsOptional() {
return Optional.ofNullable(car);
}
}
Optional 提供了的一些基础类型 —— OptionalInt
、OptionalLong
以及 OptionalDouble
,但不推荐大家使用基础类型的 Optional,因为基础类型的 Optional 不支持 map、 flatMap 以及 filter 方法,而这些却是 Optional 类常用的方法。可以使用 Optional<Int>
, Optional<Long>
, Optional<Double>
等替代。3. orElse方法的使用
orElse 中调用的方法一直都会被执行,orElseGet 方法只有在 Optional 对象不含值时才会被调用,所以使用 orElse 方法时需要谨慎, 以免误执行某些不被预期的操作。此种情况下,可使用 orElseGet 方法代替它。