Java8之使用Optional
第十章 用Optional取代null
2、Optional 类入门
防止出现NullPointerException
- 原始代码-有个人,人有车,车有保险
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; }
}
调用
public String getCarInsuranceName(Person person) {
return person.getCar().getInsurance().getName();
}
这个时候,如果一个人没有车呢,那么在get车保险的时候指定会报出NullPointerException。
很多时候可能会这么做:
public String getCarInsuranceName(Person person) {
if (person != null) {
Car car = person.getCar();
if (car != null) {
Insurance insurance = car.getInsurance();
if (insurance != null) {
return insurance.getName();
}
}
}
return "Unknown";
}
但真的不够友好
- 所以需要改动,就可以用Optional 了
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; }
}
3、应用 Optional 的几种模式
1) 创建Optional 对象
就以上改动后的代码,进行操作
a.声明一个空的Optional- empty()
Optional<Car> optCar = Optional.empty();
b.依据一个非空值创建Optional- of()
Car car = new Car();
Optional<Car> optCar = Optional.of(car);
这时候如果car是空,就会直接抛出NullPointerException
c.可接受null的Optional- ofNullable()
Optional<Car> optCar = Optional.ofNullable(car);
如果car是null,那么得到的Optional对象就是个空对象。
2) 使用 map 从 Optional 对象中提取和转换值
比如,你可能想要从insurance公司对象中提取 公司的名称。
提取名称之前,你需要检查insurance对象是否为null,代码如下:
String name = null;
if(insurance != null){
name = insurance.getName();
}
- 为了支持这种模式,Optional提供了一个map方法。(这里的map方法,从概念上讲和前面stream的map相差无几,因为其实说到底Optional也是流)
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);
如果Optional包含一个值,那函数就将该值作为参数传递给map,对该值进行转换。如 果Optional为空,就什么也不做。
3) 使用 flatMap 链接 Optional 对象
a.使用Optional获取car的保险公司名称
public String getCarInsuranceName(Optional<Person> person) {
return person.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown");
}
b.使用Optional解引用串接的Person/Car/Insurance对象
4) 默认行为及解引用 Optional 对象
Optional类提供了多种方法读取 Optional实例中的变量值。
- get()是这些方法中最简单但又最不安全的方法。如果变量存在,它直接返回封装的变量 值,否则就抛出一个NoSuchElementException异常。所以,除非你非常确定Optional 变量一定包含值,否则使用这个方法是个相当糟糕的主意。此外,这种方式即便相对于 嵌套式的null检查,也并未体现出多大的改进。
- orElse(T other)是我们在代码清单10-5中使用的方法,正如之前提到的,它允许你在 Optional对象不包含值时提供一个默认值。
- orElseGet(Supplier other)是orElse方法的延迟调用版,Supplier 方法只有在Optional对象不含值时才执行调用。如果创建默认值是件耗时费力的工作, 你应该考虑采用这种方式(借此提升程序的性能),或者你需要非常确定某个方法仅在 Optional为空时才进行调用,也可以考虑该方式(这种情况有严格的限制条件)。
- orElseThrow(Supplier exceptionSupplier)和get方法非常类似, 它们遭遇Optional对象为空时都会抛出一个异常,但是使用orElseThrow你可以定制希 望抛出的异常类型。
- ifPresent(Consumer)让你能在变量值存在时执行一个作为参数传入的 方法,否则就不进行任何操作。
5) 两个 Optional 对象的组合
假设你有这样一个方法,它接受一个Person和一个Car对象,并以此为条件对外 部提供的服务进行查询,通过一些复杂的业务逻辑,试图找到满足该组合的最便宜的保险公司:
public Insurance findCheapestInsurance(Person person, Car car) {
// 不同的保险公司提供的查询服务
// 对比所有数据
return cheapestCompany;
}
还假设你想要该方法的一个null-安全的版本,它接受两个Optional对象作为参数, 返回值是一个Optional对象,如果传入的任何一个参数值为空,它的返回值亦为空.
public Optional<Insurance> nullSafeFindCheapestInsurance(
Optional<Person> person, Optional<Car> car) {
if (person.isPresent() && car.isPresent()) {
return Optional.of(findCheapestInsurance(person.get(), car.get()));
} else {
return Optional.empty();
}
}
-
这么写也不是不可以,但是如果真的这么写的话,和之前大量的!=null又有什么区别呢
-
所以要做改进,把flatMap用到实处
-
以不解包的方式组合两个Optional对象
public Optional<Insurance> nullSafeFindCheapestInsurance(
Optional<Person> person, Optional<Car> car) {
return person.flatMap(p -> car.map(c -> findCheapestInsurance(p, c)));
}
6) 使用 filter 剔除特定的值
比如,你可能需要检查保险公司的名 称是否为“Cambridge-Insurance”。为了以一种安全的方式进行这些操作,你首先需要确定引用指 向的Insurance对象是否为null,之后再调用它的getName方法,
Insurance insurance = ...;
if(insurance != null && "CambridgeInsurance".equals(insurance.getName())){
System.out.println("ok");
}
- 如果调用filter方法,接受一个谓词作为参数。
Optional<Insurance> optInsurance = ...;
optInsurance.filter(insurance ->
"CambridgeInsurance".equals(insurance.getName()))
.ifPresent(x -> System.out.println("ok"));
分类和概括 - 表格
4、使用 Optional 的实战示例
1) 用 Optional 封装可能为 null 的值
假设你有一个Map方法,访问由key索引的值时,如果map 中没有与key关联的值,该次调用就会返回一个null。
Object value = map.get("key");
这个时候如果get为空就会报错,可以采用Optional.ofNullable方法:
Optional<Object> value = Optional.ofNullable(map.get("key"));
2) 异常与 Optional 的对比
使用静态方法Integer.parseInt(String),将 String转换为int。在这个例子中,如果String无法解析到对应的整型,该方法就抛出一个 NumberFormatException。
- 可以做一些优化,做一个工具方法
将String转换为Integer,并返回一个Optional对象
public static Optional<Integer> stringToInt(String s) {
try {
return Optional.of(Integer.parseInt(s));
} catch (NumberFormatException e) {
return Optional.empty();
}
}