文章目录
注:本文主要参考了《Java 8实战》这本书。
环境
- Ubuntu 22.04
- jdk-17.0.3.1 (兼容Java 8)
背景
现有 Insurance
、 Car
、 Person
类,定义如下:
Insurance
:
public class Insurance {
private String name;
public String getName() {
return name;
}
......
}
Car
:
public class Car {
private Insurance insurance;
public Insurance getInsurance() {
return insurance;
}
......
}
Person
:
public class Person {
private Car car;
public Car getCar() {
return car;
}
......
}
现在需要获取某个Person的Car的Insurance名字。
方法1:直接获取
最简单粗暴的写法,就是:
String name = person.getCar().getInsurance().getName();
注:像这样 a.b.c
的调用方式,貌似违反了迪米特法则。
然而,我们知道,在Java里,如果操作一个空对象,则会抛出异常。
最简单的例子:
Person person = null;
person.getCar();
此处 person
是空对象,所以调用 person.getCar()
方法时,会抛出 NullPointerException
异常:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Person.getCar()" because "person" is null
at Test0917.main(Test0917.java:8)
所以,这种方法显然是不可取的。
方法2:防御式检查
为了避免NPE,我们必须采取“防御式编程”,也就是说,在操作对象时,先要确保它不是空对象。
String name = null;
if (person != null) {
Car car = person.getCar();
if (car != null) {
Insurance insurance = car.getInsurance();
if (insurance != null) {
name = insurance.getName();
}
}
}
防御式检查可以避免NPE,然而代价是代码变得臃肿而且难以理解和维护。原先只有1行代码,现在变成了10行。
如果使用三元运算符 ? :
,固然可以简化为一行代码:
String name = (person == null)
? null
: ((person.getCar() == null)
? null
: ((person.getCar().getInsurance() == null)
? null
: (person.getCar().getInsurance().getName())));
但是这行代码实在是太令人费解了,同时非常难以维护。
方法3:Java 8的Optional
概述
方法1很简单,但有漏洞,方法2弥补了漏洞,但带来了复杂度。那如何才能兼顾简单与安全呢?
Java 8引入了Optional类,这是一个封装“Optional值”的类。顾名思义,既然是optional,其封装的值可能存在,也可能不存在。
举个例子来说,一个 Optional<Car>
对象,可能封装了一个非空的 Car
对象,也可能封装了一个 null
对象。
现在简单看一下创建Optional的语法:
- 创建一个空的Optional对象:
Optional<Car> car1 = Optional.empty();
- 创建一个非空的Optional对象:
Optional<Car> car2 = Optional.of(car); // car不能为null
其中, car
是一个Car对象,而且必须是非空的,否则,这一步会直接抛出NPE。
- 如果想创建一个可接受空值的Optional对象:
Optional<Car> car3 = Optional.ofNullable(car); // car可以为null
从Optional对象获取其封装对象:
get()
:
Optional<Car> car4;
......
Car car = car4.get(); // 获取封装的Car对象,若其为空,则抛出NoSuchElementException
orElse()
:
Car car = car4.orElse(new Car()); // 获取封装的Car对象,若其为空,则返回指定值
map()
看到这里,你可能还是一头雾水,到底Optional能给我们带来什么好处?
Optional的神奇之处在于,它和流(Stream)的用法非常相似,可以做 map()
、 filter()
等操作。事实上,它就相当于只包含0个或者1个对象的流。
在方法1中:
String name = person.getCar().getInsurance().getName();
可见,代码逻辑是依次获取Car、Insurance、Name。
通过Optional,可以把它转换为如下操作:
Optional<Person> optPerson = Optional.ofNullable(person);
String name = optPerson.map(Person::getCar)
.map(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown");
本例中, map()
操作把Optional所持有的对象做了映射,比如本来是持有Person( Optional<Person>
),变成持有Car( Optional<Car>
),再变成持有String( Optional<String>
),最终获取String对象。
如果持有对象不为空,则对其做map操作,若持有对象为空,则不做处理。这就大大简化了代码,提高了可读性和可维护性。
测试
完整的测试如下:
- 测试1:Person,Car,Insurance都不为空:
Insurance insurance1 = new Insurance("ABC");
Car car1 = new Car(insurance1);
Person person1 = new Person(car1);
Optional<Person> optPerson1 = Optional.ofNullable(person1);
System.out.println(optPerson1.map(Person::getCar));
System.out.println(optPerson1.map(Person::getCar).map(Car::getInsurance));
System.out.println(optPerson1.map(Person::getCar).map(Car::getInsurance).map(Insurance::getName));
System.out.println(optPerson1.map(Person::getCar).map(Car::getInsurance).map(Insurance::getName).orElse("Unknown"));
运行结果如下:
Optional[Car{insurance=Insurance{name='ABC'}}]
Optional[Insurance{name='ABC'}]
Optional[ABC]
ABC
- 测试2:Person和Car不为空,Insurance为空:
Car car2 = new Car(null);
Person person2 = new Person(car2);
Optional<Person> optPerson2 = Optional.ofNullable(person2);
System.out.println(optPerson2.map(Person::getCar));
System.out.println(optPerson2.map(Person::getCar).map(Car::getInsurance));
System.out.println(optPerson2.map(Person::getCar).map(Car::getInsurance).map(Insurance::getName));
System.out.println(optPerson2.map(Person::getCar).map(Car::getInsurance).map(Insurance::getName).orElse("Unknown"));
运行结果如下:
Optional[Car{insurance=null}]
Optional.empty
Optional.empty
Unknown
- 测试3:Person不为空,Car和Insurance为空:
Person person3 = new Person(null);
Optional<Person> optPerson3 = Optional.ofNullable(person3);
System.out.println(optPerson3.map(Person::getCar));
System.out.println(optPerson3.map(Person::getCar).map(Car::getInsurance));
System.out.println(optPerson3.map(Person::getCar).map(Car::getInsurance).map(Insurance::getName));
System.out.println(optPerson3.map(Person::getCar).map(Car::getInsurance).map(Insurance::getName).orElse("Unknown"));
运行结果如下:
Optional.empty
Optional.empty
Optional.empty
Unknown
- 测试4:Person,Car,Insurance都为空:
Optional<Person> optPerson4 = Optional.ofNullable(null);
System.out.println(optPerson4.map(Person::getCar));
System.out.println(optPerson4.map(Person::getCar).map(Car::getInsurance));
System.out.println(optPerson4.map(Person::getCar).map(Car::getInsurance).map(Insurance::getName));
System.out.println(optPerson4.map(Person::getCar).map(Car::getInsurance).map(Insurance::getName).orElse("Unknown"));
运行结果如下:
Optional.empty
Optional.empty
Optional.empty
Unknown
可见,在任何情况下,都能得到预期的结果,不会报错。
flatMap()
既然Person不一定拥有Car,Car也不一定拥有Insurance,所以应该都是optional的。
假设修改类如下:
Car
:
public class Car {
private Insurance insurance;
public Optional<Insurance> getInsurance() {
return Optional.ofNullable(insurance);
}
......
}
Person
:
public class Person {
private Car car;
public Optional<Car> getCar() {
return Optional.ofNullable(car);
}
......
}
注意:要确保 getXxx()
返回的Optional对象本身不要为空,否则,就又得加上判断逻辑了。
相应的, map()
操作需要转换为 flatMap()
:
Optional<Person> optPerson = Optional.ofNullable(person);
String name = optPerson.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown");
注意:这里之所以使用 flatMap()
,是因为 Person::getCar
返回的是 Optional<Car>
,需要用 flatMap()
将其扁平化(去掉中间层的Optional)。
注意:要确保 getXxx()
返回的Optional对象本身不要为空,否则,在调用 flatMap()
时会抛出NPE。
测试
下面是完整的测试:
- 测试1:Person,Car,Insurance都不为空:
Insurance insurance1 = new Insurance("ABC");
Car car1 = new Car(insurance1);
Person person1 = new Person(car1);
Optional<Person> optPerson1 = Optional.ofNullable(person1);
System.out.println(optPerson1.flatMap(Person::getCar));
System.out.println(optPerson1.flatMap(Person::getCar).flatMap(Car::getInsurance));
System.out.println(optPerson1.flatMap(Person::getCar).flatMap(Car::getInsurance).map(Insurance::getName));
System.out.println(optPerson1.flatMap(Person::getCar).flatMap(Car::getInsurance).map(Insurance::getName).orElse("Unknown"));
运行结果如下:
Optional[Car{insurance=Insurance{name='ABC'}}]
Optional[Insurance{name='ABC'}]
Optional[ABC]
ABC
- 测试2:Person和Car不为空,Insurance为空:
Car car2 = new Car(null);
Person person2 = new Person(car2);
Optional<Person> optPerson2 = Optional.ofNullable(person2);
System.out.println(optPerson2.flatMap(Person::getCar));
System.out.println(optPerson2.flatMap(Person::getCar).flatMap(Car::getInsurance));
System.out.println(optPerson2.flatMap(Person::getCar).flatMap(Car::getInsurance).map(Insurance::getName));
System.out.println(optPerson2.flatMap(Person::getCar).flatMap(Car::getInsurance).map(Insurance::getName).orElse("Unknown"));
运行结果如下:
Optional[Car{insurance=null}]
Optional.empty
Optional.empty
Unknown
- 测试3:Person不为空,Car和Insurance为空:
Person person3 = new Person(null);
Optional<Person> optPerson3 = Optional.ofNullable(person3);
System.out.println(optPerson3.flatMap(Person::getCar));
System.out.println(optPerson3.flatMap(Person::getCar).flatMap(Car::getInsurance));
System.out.println(optPerson3.flatMap(Person::getCar).flatMap(Car::getInsurance).map(Insurance::getName));
System.out.println(optPerson3.flatMap(Person::getCar).flatMap(Car::getInsurance).map(Insurance::getName).orElse("Unknown"));
运行结果如下:
Optional.empty
Optional.empty
Optional.empty
Unknown
- 测试4:Person,Car,Insurance都为空:
Optional<Person> optPerson4 = Optional.ofNullable(null);
System.out.println(optPerson4.flatMap(Person::getCar));
System.out.println(optPerson4.flatMap(Person::getCar).flatMap(Car::getInsurance));
System.out.println(optPerson4.flatMap(Person::getCar).flatMap(Car::getInsurance).map(Insurance::getName));
System.out.println(optPerson4.flatMap(Person::getCar).flatMap(Car::getInsurance).map(Insurance::getName).orElse("Unknown"));
运行结果如下:
Optional.empty
Optional.empty
Optional.empty
Unknown
总结
Optional
类帮助我们处理可能存在的null值。它的用法类似于流(Stream),简单明了,可以精简代码,提高代码可读性和可维护性。
Optional的常用方法如下:
方法 | 描述 |
---|---|
empty() | 返回一个空的Optional实例 |
filter() | 类似流的filter |
flatMap() | 类似流的flatMap |
get() | 获取封装的对象,若其为空,则抛出NoSuchElementException |
ifPresent() | 如果值存在,则运行传入的Consumer |
isPresent() | 值是否存在 |
map() | 类似流的map |
of() | 返回封装指定值的Optional对象,若指定值为null,则抛出NPE |
ofNullable() | 同of(),但允许null值 |
orElse() | 获取封装的对象,若其为空,则返回指定值 |
orElseGet() | 获取封装的对象,若其为空,则运行传入的Supplier并返回其结果 |
orElseThrow() | 获取封装的对象,若其为空,则运行传入的Supplier并抛出其生成的异常 |
参考
https://livebook.manning.com/book/java-8-in-action/