java 8使用Optional取代null

在前面的章节中我们已经使用过Optional了,这里介绍Optional的更多用法。

假定有如下数据模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Person {
private Car car;

public Car getCar() {
return car;
}
}

public class Car {
private Insurance insurance;

public Insurance getInsurance() {
return insurance;
}
}

public class Insurance {
private String name;

public String getName() {
return name;
}
}

那么,下面这段代码有什么问题?

1
2
3
public String getCarInsuranceName(Person person) {
return person.getCar().getInsurance().getName();
}

person可能为空,ca也可能为空,insurance也可能为空,任何一个对象为空都会抛出空指针异常。

所以,常规做法我们需要做空值判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public String getCarInsuranceName(Person person) {
if (null != person) {
Car car = person.getCar();

if (null != car) {
Insurance insurance = car.getInsurance();

if (null != insurance) {
return insurance.getName();
}
}
}
return null;
}

可以看到N多层次的空值判断,这种方式不具备可扩展性,可读性也不好。

下面我们使用java 8提供的Optional来解决这个问题。
数据模型修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Person {
private Optional<Car> car;

public Optional<Car> getCar() {
return car;
}
}

public class Car {
private Optional<Insurance> insurance;

public Optional<Insurance> getInsurance() {
return insurance;
}
}

public class Insurance {
private String name;

public String getName() {
return name;
}
}

根据车主获取保险公司名字的方法

1
2
3
4
5
6
public String getCarInsuranceName(Optional<Person> person) {
return person.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("unknown");
}

Person、Car、Insurance任意一个对象为空,结果都会返回unknown。
very简洁有木有?

Optional入门

创建Optional对象

  • 声明一个空的Optional

    1
    Optional<Person> optPerson = Optional.empty();
  • 根据非空值创建

    1
    2
    Person person = new Person();
    Optional<Person> optional = Optional.of(person);

如果person为null,会立即抛出空指针异常,而不是等到访问person的属性时才返回一个错误。

  • 可接受null的Optional
    1
    Optional<Person> optional = Optional.ofNullable(person);

如果person为null,那么得到的optional就是空对象。

使用map从Optional提取和转换值

从insurance公司提取公司的名称,提取名称前,你需要检查insurance是否为null,代码如下:

1
2
3
4
String name = null;
if (null != insurance) {
name = insurance.getName();
}

为了支持这种模式,Optional提供了map方法。

1
2
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);

使用flatMap链接Optional对象

在之前使用流时使用了它的flatMap方法,它接受一个函数作为参数,返回值是另一个流。这个方法会应用到流中的每个元素,最终形成一个新的流的流。但是flatMap会用流的内容替换每个新生成的流,即方法生成的各个流会被合并或者扁平化为一个单一的流。
如果我们使用Optional.map从Optional提取Insurance的name会编译不通过。

1
2
3
4
5
6
public String getCarInsuranceName(Optional<Person> person) {
return person.map(Person::getCar)
.map(Car::getInsurance)
.map(Insurance::getName)
.orElse("unknown");
}

因为person.map(Person::getCar)返回的是一个Optional<Optional>对象,所以它调用map(Car::getInsurance)是非法的。但是我们可以借助Optional.flatMap来实现,如文章最开始所述。

2个Optional的组合

假定有一个方法,接收一个Person和Car对象,并以此为条件对外提供服务进行查询,通过一些业务逻辑,返回满足该组合的最便宜的保险公司。

1
2
3
4
5
public Insurance findCheapestInsurance(Person person,Car car) {
// 不同保险公司提供的查询服务
// 对比所有数据
return cheapestCompany;
}

假设我们需要一个Null-safe版本的方法,接收2个参数Optional和Optional,返回一个Optional对象。如果传入的任意一个参数为空,那么返回一个空的Optional。
由于Optional提供了一个isPersent方法,所以可能的实现如下:

1
2
3
4
5
6
public Insurance nullSafeFindCheapestInsurance(Optional<Person> person,Optional<Car> car) {
if (person.isPresent() && car.isPresent()) {
return Optional.of(findCheapestInsurance(person.get(),car.get()));
}
return Optional.empty();
}

但实际上,我们完全可以使用flatMap和map来实现。

1
2
3
public Optional<Insurance> nullSafeFindCheapestInsurance(Optional<Person> person, Optional<Car> car) {
return person.flatMap(p -> car.map(c -> findCheapestInsurance(p, c)));
}

使用filter剔除特定的值

filter方法接收一个谓词作为参数,如果Optional对象的值存在,并且符合谓词的条件,filter方法就返回其值,否则就返回一个空的Optional对象。
示例:

1
2
Optional<Insurance> optInsurance = ...;
optInsurance.filter(insurance -> "CambriadeInsurance".equals(insurance.getName())).ifPersent(System.out::println);

示例2:找出年龄大于或等于minAge参数的Person所对应的保险公司列表。

1
2
3
4
5
6
7
public String getCarInsuranceName(Optional<Person> person,int minAge) {
return person.filter(p -> p.getAge() >= minAge)
.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown");
}

使用Optional的实战示例

使用Optional封装可能的null值

假设你有一个Map<String,Object>方法,访问由key索引的值时,如果map中没有与key关联的值,就会返回一个null。

1
Object value = map.get(key);

你可以使用Optional封装map的返回值

1
Optional<Object> value = Optional.ofNullable(map.get(key));

每次你希望安全的将潜在的null的对象进行转换,将其转换为Optional对象时,就可以考虑使用这种方法。

异常与Optional

由于某种原因,函数无法返回值,这时除了返回null,还可能抛出一个异常,比如Integer.parseInt。

你可以使用Optional对操作结果进行包装

1
2
3
4
5
6
7
public Optional<Integer> stringToInt(String str) {
try {
return Optional.of(Integer.parseInt(str));
} catch (NumberFormatException e) {
return Optional.empty();
}
}

你可以将很多类似的操作封装在一个工具类中,比如叫OptionalUtility。以后就可以直接调用OptionalUtility.stringToInt,就能将字符串转换为一个Optional,而无需记住你在其中封装了笨重的try/catch逻辑了。

注意:不推荐使用基础类型的Optional,比如OptionalInt,OptionalLong,OptionalDouble等等,因为它们不支持map、flatMap和filter等方法,而这些确实Optional中最有用的方法。

综合示例:从属性中读取一个int属性值(属性不存在则返回0)

1
2
3
4
5
6
public int readDuration(Properties p, String key) {
return Optional.ofNullable(p.getProperty(key))
.flatMap(OptionalUtility::stringToInt)
.filter(i -> i > 0)
.orElse(0);
}

参考《java8实战》

Donny wechat
欢迎关注我的个人公众号
打赏,是超越赞的一种表达。
Show comments from Gitment