Optional
Java8中引入java.util.Optional
举例来说,如果一个人可能有也可能没有车,那么Person类内部的car变量就不应该声明为Car,遭遇某人没有车时不应该把null引用赋值给它,而是应该将其声明为Optional
在代码中始终如一地使用Optional,能非常清晰地界定出变量值的缺失是结构上的问题,还是算法上的缺陷,抑或是数据中的问题。
引入Optional类的意图并非要消除每一个null引用。与此相反,它的目标是帮助更好地设计出普适的API,以便看到方法签名,就能了解它是否接受一个Optional的值。这种强制会积极的将变量从Optional中解包出来,直面缺失的变量值。
创建Optional对象
Optional.empty,创建一个空的Optional对象
1
Optional<Car> optCar = Optional.empty();
Optional.of,依据一个非空值创建一个Optional对象
1
Optional<Car> optCar = Optional.of(car);
如果car是一个null,这段代码会立即抛出一个NullPointerException,而不是等到试图访问car的属性值时才返回一个错误。
- Optional.ofNullable,创建一个允许null值的Optional对象
1
Optional<Car> optCar = Optional.ofNullable(car);
如果car是null,那么得到的Optional对象就是个空对象。
不过get()方法在遭遇到空的Optional对象时也会抛出异常,所以不按照约定的方式使用它,又会让再度陷入由null引起的代码维护的梦魇。
使用map从Optional对象中提取和转换值
map操作会将提供的函数应用于流的每个元素。你可以把Optional对象看成一种特殊的集合数据,它至多包含一个元素。
如果Optional包含一个值,那函数就将该值作为参数传递给map,对该值进行转换。如 果Optional为空,就什么也不做。
1 | String name = null; |
优化1
2
3Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);
使用flatMap链接Optional对象
使用流时,flatMap方法接受一个函数作为参数,这个函数的返回值是另一个流。
这个方法会应用到流中的每一个元素,最终形成一个新的流。但是flagMap会用流的内容替换每个新生成的流。1
2
3
4
5public String getCarInsuranceName(Optional<Person> person) { return person.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown");
}
使用Optional解引用串接对象
上例中,以Optional封装的Person入手,对其调用flatMap(Person::getCar)。
flatMap将某个Function作为参数,被传递给Optional封装的Person对象,对其进行转换。Function的具体表现是一个方法引用,即对Person对象的getCar方法进行调用。由于该方法返回一个Optional
可以将这种合并操作简单地看成把两个Optional对象结合在一起,如果其中有一个对象为空,就构成一个空的Optional对象。如果你对一个空的Optional对象调用flatMap,结果不会发生任何改变, 返回值也是个空的Optional对象。
如果Optional封装了一个Person对象,传递给flapMap的Function,就会应用到Person上对其进行处理。这个例子中,由于Function的返回值已经是一个Optional对象,flapMap方法就直接将其返回。
如果调用链上的任何一个方法返回一个空的Optional,那么结果就为空,否则返回的值就是你期望的保险公司的名称。
如何读出这个值呢?毕竟最后得到的这个对象还是个Optional
无法序列化
由于Optional类设计时就没特别考虑将其作为类的字段使用,所以它也并未实现Serializable接口。由于这个原因,如果用了某些要求序列化的库或者框架,在模型中使用Optional,有可能引发应用程序故障。
如果你一定要实现序列化的域模型,作为替代方案,建议像下面这个例子那样,提供一个能访问声明为Optional、变量值可能缺失的接口。1
2
3
4
5
6public class Person {
private Car car;
public Optional<Car> getCarAsOptional(){
return Optional.ofNullable(car);
}
}
两个Optional对象的组合
1 | if (person.isPresent() && car.isPresent()){ |
以不解包的方式组合两个Optional对象1
return person.flatMap(p -> car.map(c -> findCheapestInsurance(p, c)));
使用filter剔除特定的值
filter方法接受一个谓词作为参数。如果Optional对象的值存在,并且它符合谓词的条件,filter方法就返回其值;否则它就返回一个空的Optional对象。1
2
3if(insurance != null && "CambridgeInsurance".equals(insurance.getName())){
System.out.println("ok");
}
优化1
2optInsurance.filter(insurance -> "CambridgeInsurance".equals(insurance.getName()))
.ifPresent(x -> System.out.println("ok"));
1 | return person.filter(p -> p.getAge() >= minAge) |
用Optional封装可能为null的值
1 | Object value = map.get("key"); |
优化1
Optional<Object> value = Optional.ofNullable(map.get("key"));
1 | return Optional.ofNullable(props.getProperty(name)) |
异常与Optional的对比
1 | try { |
建议将多个类似的方法封装到一个工具类中。
基础类型的Optional对象和避免使用
基础类型
- OptionalInt
- OptionalLong
- OptionalDouble
不推荐使用基础类型的Optional,因为基础类型的Optional不支持map、flatMap以及filter方法,而这些却是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>)
能在变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作。 - of
将指定值用Optional封装之后返回,如果该值为null,则抛出一个NullPointerException异常。 - ofNullable
将指定值用Optional封装之后返回,如果该值为null,则返回一个空的Optional对象。 - flatMap
如果值存在,就对该值执行提供的mapping函数调用,返回一个 Optional 类型的值,否则就返回一个空的Optional对象 - map
如果值存在,就对该值执行提供的mapping函数调用将指定值用Optional封装之后返回,如果该值为null,则返回一个空的 Optional对象。