Java8实战-总结42
- 用Optional取代null
- 应用 Optional 的几种模式
- 默认行为及解引用 Optional 对象
- 两个 Optional 对象的组合
- 使用 filter 剔除特定的值
用Optional取代null
应用 Optional 的几种模式
默认行为及解引用 Optional 对象
采用orElse
方法读取这个变量的值,使用这种方式还可以定义一个默认值,遭遇空的Optional
变量时,默认值会作为该方法的调用返回值。Optional
类提供了多种方法读取Optional
实例中的变量值。
get()
是这些方法中最简单但又最不安全的方法。如果变量存在,它直接返回封装的变量值,否则就抛出一个NoSuchElementException
异常。所以,除非非常确定Optional
变量一定包含值,否则使用这个方法是个相当糟糕的主意。此外,这种方式即便相对于嵌套式的null
检查,也并未体现出多大的改进。orElse(T other)
允许在Optional
对象不包含值时提供一个默认值。orElseGet(Supplier<? extends T> other)
是orElse
方法的延迟调用版,Supplier
方法只有在Optional
对象不含值时才执行调用。如果创建默认值是件耗时费力的工作,应该考虑采用这种方式(借此提升程序的性能),或者需要非常确定某个方法仅在Optional
为空时才进行调用,也可以考虑该方式(这种情况有严格的限制条件)。orElseThrow(Supplier<? extends X> exceptionSupplier)
和get
方法非常类似,它们遭遇Optional
对象为空时都会抛出一个异常,但是使用orElseThrow
可以定制希望抛出的异常类型。ifPresent(Consumer<? super T>)
能在变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作。
Optional
类和Stream
接口的相似之处,远不止map
和flatMap
这两个方法。还有第三个方法filter
,它的行为在两种类型之间也极其相似。
两个 Optional 对象的组合
现在,假设有这样一个方法,它接受一个Person
和一个Car
对象,并以此为条件对外部提供的服务进行查询,通过一些复杂的业务逻辑,试图找到满足该组合的最便宜的保险公司:
public Insurance findCheapestInsurance(Person person, Car car) { // 不同的保险公司提供的查询服务// 对比所有数据return cheapestCompany;
}
还假设想要该方法的一个null
安全的版本,它接受两个Optional
对象作为参数,返回值是一个Optional<Insurance>
对象,如果传入的任何一个参数值为空,它的返回值亦为空。Optional
类还提供了一个isPresent
方法,如果Optional
对象包含值,该方法就返回true
,所以第一想法可能是通过下面这种方式实现该方法:
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(); }
}
这个方法具有明显的优势,从它的签名就能非常清楚地知道无论是person
还是car
,它的值都有可能为空,出现这种情况时,方法的返回值也不会包含任何值。不幸的是,该方法的具体实现和之前曾经实现的null
检查太相似了:方法接受一个Person
和一个Car
对象作为参数,而二者都有可能为null
。利用Optional
类提供的特性,可以用更好的方式来实现这个方法。
以不解包的方式组合两个Optional对象结合本节中介绍的map和flatMap方法,用一行语句重新实现之前出现的nullSafeFindCheapestInsurance()方法。答案:可以像使用三元操作符那样,无需任何条件判断的结构,以一行语句实现该方法,
代码如下。public Optional<Insurance> nullSafeFindCheapestInsurance(Optional<Person> person, Optional<Car> car) { return person.flatMap(p -> car.map(c -> findCheapestInsurance(p, c)));
}
这段代码中,对第一个Optional对象调用flatMap方法,如果它是个空值,传递给它
的Lambda表达式不会执行,这次调用会直接返回一个空的Optional对象。反之,如果person
对象存在,这次调用就会将其作为函数Function的输入,并按照与flatMap方法的约定返回
一个Optional<Insurance>对象。这个函数的函数体会对第二个Optional对象执行map操
作,如果第二个对象不包含car,函数Function就返回一个空的Optional对象,整个
nullSafeFindCheapestInsuranc方法的返回值也是一个空的Optional对象。最后,如果
person和car对象都存在,作为参数传递给map方法的Lambda表达式能够使用这两个值安全
地调用原始的findCheapestInsurance方法,完成期望的操作。
Optional
类和Stream
接口的相似之处远不止map
和flatMap
这两个方法。还有第三个方法filter
,它的行为在两种类型之间也极其相似。
使用 filter 剔除特定的值
经常需要调用某个对象的方法,查看它的某些属性。比如,可能需要检查保险公司的名称是否为“Cambridge-Insurance
”。为了以一种安全的方式进行这些操作,首先需要确定引用指向的Insurance
对象是否为null
,之后再调用它的getName
方法,如下所示:
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"));
filter
方法接受一个谓词作为参数。如果Optional
对象的值存在,并且它符合谓词的条件,filter
方法就返回其值;否则它就返回一个空的Optional
对象。如果还记得可以将Optional
看成最多包含一个元素的Stream
对象,这个方法的行为就非常清晰了。如果Optional
对象为空,它不做任何操作,反之,它就对Optional
对象中包含的值施加谓词操作。如果该操作的结果为true
,它不做任何改变,直接返回该Optional
对象,否则就将该值过滤掉,将Optional
的值置空。
对Optional对象进行过滤假设在Person/Car/Insurance 模型中,Person还提供了一个方法可以取得
Person对象的年龄,使用下面的签名改写之前的getCarInsuranceName方法:
public String getCarInsuranceName(Optional<Person> person, int minAge)
找出年龄大于或者等于minAge参数的Person所对应的保险公司列表。答案:可以对Optional封装的Person对象进行filter操作,设置相应的条件谓词,
即如果person的年龄大于minAge参数的设定值,就返回该值,并将谓词传递给filter方法,
代码如下所示。
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`类中的方法进行了分类和概括。