Administrator
发布于 2020-12-23 / 1194 阅读 / 0 评论 / 0 点赞

Java8之使用Optional

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为空,就什么也不做。

image-20201220232011807

3) 使用 flatMap 链接 Optional 对象

image-20201220233602966

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对象

image-20201220233925639

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")); 

分类和概括 - 表格

image-20201221001544168

image-20201221001605937

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();
     }
} 

评论