为什么使用Optional类?还是一段出现了很多次的代码,我们定义一个Response类用于描述广告接口的返回,内部包含一个广告Ad,Ad内部包含素材信息Material,素材内部包含链接信息。类定义如下:
@Data
class Ad{
Material material;
// other
}
@Data
class Material{
String url;
// other
}
@Data
class Response{
int code;
Ad ad;
}
由于出现了对象的层级引用,使得我们想要获取最里面的属性时,必须经过多层取属性操作,只要有一处为空,就会报NPE,所以这能写成下面的形式:
if (response != null) {
if (response.ad != null) {
if (response.ad.material != null) {
System.out.println(response.ad.material.url);
}
}
}
当然这里正常要使用get方法而不是直接点号取属性,这里只是一个例子。这样写本身没有任何问题,代码也很健壮,只是可读性比较差,取一个属性,竟让需要如此之多的判空语句。所以,java8引入了Optional类来解决这个问题。
看下Optional类如何完成这个功能:
String s = Optional.ofNullable(response)
.map(Response::getAd)
.map(Ad::getMaterial)
.map(Material::getUrl)
.orElse("unknown");
System.out.println(s);
看上去是不是好了很多?
用法:
简单说,Optional的思路就是提供一个容器类或者包装类打包我们的对象,然后把判空逻辑封装在Optional类内部,再提供判空后的api来供我们取值。所以,如果我们想要取一个对象的属性,要先使用Optional的两个静态方法把对象包起来。
of和ofNullable方法的区别在于,如果对象是null,of方法会报错。而ofNullable方法则会返回空的Optional,至于这个空的Optional是什么,会在后面的实现部分解释。通常我们需要使用的是ofNullable方法。
然后我们可以使用Optional的map或者flatMap方法取值。这里需要注意,stream类中也有相同名字的方法,但是二者并没有关系。在Optional类中,这两个方法用于映射值,区别在于map方法会把映射以后的值打包在新的Optional实例中,而flatMap不会打包,但是要求我们的mapper映射函数返回值为打包以后的Optional实例。换句话说map函数的mapper函数类似e->e.getXXX(),而flatMap函数的mapper函数类似e->Optional.ofNullable(e.getXXX()),flatMap函数存在的意义在于,可能我们会在mapper函数中实现打包,这时map时我们就不在需要打包。通常,我们只需要使用map函数即可。
map的例子在上面已经有了,下面是flatMap的例子:
String s1 = Optional.ofNullable(response)
.flatMap(e->Optional.ofNullable(e.getAd()))
.flatMap(e->Optional.ofNullable(e.getMaterial()))
.flatMap(e->Optional.ofNullable(e.getUrl()))
.orElse("unknown");
System.out.println(s1);
最终取值时,Optional提供了3个方法。当检测Optional里的值为空时,orElse返回一个指定的默认值,orElseGet允许我们提供一个Supplier接口来生成默认值,orElseThrow跑出指定的异常。
String s = Optional.ofNullable(response)
.map(Response::getAd)
.map(Ad::getMaterial)
.map(Material::getUrl)
.orElseGet(()->"unknown" + Math.random());
System.out.println(s);
String s = Optional.ofNullable(response)
.map(Response::getAd)
.map(Ad::getMaterial)
.map(Material::getUrl)
.orElseThrow(NullPointerException::new);
System.out.println(s);
最后,Optional还为我们提供了过滤功能,filter函数允许我们提供一个Predict接口,用于过滤对象的值,如果对象为空或判定为false,返回空的Optional,否则返回原来的Optional。
String raw = "abced";
String r = Optional.ofNullable(raw).filter(e->e.length() < 3).orElse("INVALID");
System.out.println(r);
至此,关于如何使用Optional避免嵌套的判空已经介绍完,下面看下Optional类的实现。
实现:
Optional类是一个泛型容器类。内部只有一个实例变量:
private final T value;
该变量存放被包装的对象。
除此之外,还有一个静态变量:
private static final Optional<?> EMPTY = new Optional<>();
如果被包装的变量为null,就会返回这个静态变量。下面看下构造函数:
private Optional() {
this.value = null;
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
无参形式用于构造被打包对象为null的Optional实例,之前的静态变量EMPTY,就是调用该函数构造的。含参构造函数用于构造被打包对象不为null的Optional实例。
注意构造函数为私有的,所以只能使用下面的静态方法来构造Optional打包实例:
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
of如果接受null参数会报错,ofNullable如果接受null参数会返回EMPTY实例,即空的打包实例。
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
这里的map方法,先检测被打包对象是否为null,如果是就返回EMPTY,否则就调用mapper映射函数,最终再封装成Optional实例返回。
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}
而flatMap则不会将最终结果打包成Optional实例。
下面是filter方法:
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
如果被打包对象为null或者没有通过predicate检测,就会返回EMPTY,否则返回原Optional对象。
最后是orElse,orElseGet,orElseThrow:
public T orElse(T other) {
return value != null ? value : other;
}
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}