Java8学习笔记之应用Optional的几种模式
1、创建Optional对象
1)声明一个空的Optional
可以通过静态工厂方法Optional.empty,创建一个空的Optional 对象:
Optional<Car> optCar = Optional.empty();
2)依据一个非空值创建Optional
还可以使用静态工厂方法Optional.of,依据一个非空值创建一个Optional对象:
Optional<Car> optCar = Optional.of(car);
如果car是一个null,代码会立即抛出一个NullPointerException,而不是等到你访问car的属性值时才返回一个错误。
3)可接受null的Optional
使用静态工厂方法Optional.ofNullable,你可以创建一个允许null值的Optional对象:
Optional<Car> optCar = Optional.ofNullable(car);
如果car是null,那么得到的Optional对象就是个空对象。
2、使用map从Optional 对象中提取和转换值
从对象中提取信息是一种比较常见的模式。Optional提供了一个map方法。它的工作方式如下:
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);
map操作会将提供的函数应用于流的每个元素,你可以把Optional对象看成一种特殊的集合数据,它至多包含一个元素。如果Optional包含一个值,那函数就将该值作为参数传递给map,对该值进行转换。如果Optional为空,就什么也不做。
Stream和Optional的map方法对比3、使用flatMap链接Optional对象
两层的optional对象flatMap方法:使用流时,flatMap方法接受一个函数作为参数,这个函数的返回值是另一个流。这个方法会应用到流中的每一个元素,终形成一个新的流的流。flagMap会用流的内容替换每个新生成的流。
Stream和Optional的flagMap方法对比传递给流的flatMap方法会将每个正方形转换为另一个流中的两个三角形。那么,map操作的结果就包含有三个新的流,每一个流包含两个三角形,但flatMap方法会将这种两层的流合并为一个包含六个三角形的单一流。
传递给optional的flatMap方法的函数会将原始包含正方形的optional对象转换为包含三角形的optional对象。如果将该方法传递给map方法,结果会是一个Optional对象,而这个Optional对象中包含了三角形;但flatMap方法会将这种两层的Optional对象转换为包含三角形的单一Optional对象。
public String getCarInsuranceName(Optional<Person> person) {
return person.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown"); //如果Optional的结果值为空,设置默认值
}
这种方式的优点,它通过类型系统让你的域模型中隐藏的知识显式地体现在代码中。声明方法接受一个Optional参数,或者将结果作为Optional类型返回,让方法的使用者,很清楚地知道它可以接受空值,或者它可能返回一个空值。
使用Optional解引用串接的Person/Car/Insurance以Optional封装的Person入手,对其调用flatMap(Person::getCar)。这种调用逻辑上可以划分为三步。
第一步,某个Function作为参数,被传递给由Optional封装的Person对象,对其进行转换。这个场景中,Function的具体表现是一个方法引用,即对Person对象的getCar方法进行调用。由于该方法返回一个Optional<Car>类型的对象,Optional内的Person也被转换成了这种对象的实例,结果就是一个两层的Optional对象,最终它们会被flagMap操作合并。可以将这种合并操作简单地看成把两个Optional对象结合在一起,如果其中有一个对象为空,就构成一个空的Optional对象。对一个空的Optional对象调用flatMap,结果不会发生任何改变, 返回值也是个空的Optional对象。如果Optional封装了一个Person对象,传递给flapMap的Function,就会应用到Person上对其进行处理。上例中,由于Function的返回值已经是一个Optional对象,flapMap方法就直接将其返回。
第二步,它会将Optional<Car>转换为Optional<Insurance>。
第三步,会将Optional<Insurance>转化为Optional<String>对象,由于Insurance.getName() 方法的返回类型为String,不需要进行flapMap操作了。
由于Optional类设计时没特别考虑将其作为类的字段使用,所以它也并未实现Serializable接口。如果你的应用使用了某些要求序列化的库或者框架,在域模型中使用Optional,有可能引发应用程序故障。用Optional声明域模型中的某些类型是个不错的主意,尤其是你需要遍历有可能全部或部分为空,或者可能不存在的对象时。如果你一定要实现序列化的域模型,替代方案是,提供一个能访问声明为Optional、变量值可能缺失的接口,如下:
public class Person {
private Car car;
public Optional<Car> getCarAsOptional() {
return Optional.ofNullable(car);
}
}
4、默认行为及解引用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>)让你能在变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作。
5、两个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对象:
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方法,完成期望的操作。
6、使用filter剔除特定的值
如果经常需要调用某个对象的方法,查看它的某些属性,可以使用Optional对象的filter方法。
Optional<Insurance> optInsurance = ...;
optInsurance.filter(insurance -> "CambridgeInsurance".equals(insurance.getName())) .ifPresent(x -> System.out.println("ok"));
filter方法接受一个谓词作为参数。如果Optional对象的值存在,并且它符合谓词的条件,filter方法就返回其值;否则它就返回一个空的Optional对象。如果Optional 对象为空,它不做任何操作,反之,它就对Optional对象中包含的值施加谓词操作。如果该操作的结果为true,它不做任何改变,直接返回该Optional对象,否则就将该值过滤掉,将Optional的值置空。
对Optional对象进行过滤:
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类的方法说明:
empty:返回一个空的Optional实例。
filter:如果值存在并且满足提供的谓词,就返回包含该值的Optional对象;否则返回一个空的Optional对象。
flatMap:如果值存在,就对该值执行提供的mapping函数调用,返回一个Optional类型的值,否则就返回一个空的Optional对象。
get:如果该值存在,将该值用Optional封装返回,否则抛出一个NoSuchElementException异常。
ifPresent:如果值存在,就执行使用该值的方法调用,否则什么也不做。
isPresent:如果值存在就返回true,否则返回false。
map:如果值存在,就对该值执行提供的mapping函数调用。
of:将指定值用Optional封装之后返回,如果该值为null,则抛出一个NullPointerException异常。
ofNullable:将指定值用Optional封装之后返回,如果该值为null,则返回一个空的Optional对象。
orElse:如果有值则将其返回,否则返回一个默认值。
orElseGet:如果有值则将其返回,否则返回一个由指定的Supplier接口生成的值。
orElseThrow:如果有值则将其返回,否则抛出一个由指定的Supplier接口生成的异常。
--参考文献《Java8实战》