概述
Java 8 引入了一个非常强大的特性就是 Optional
类,其主要解决的问题就是我们编程时常常遇到的空指针异常(NPE-NullPointerException)。在传统的编程方法里,通常都是以 if-else 条件语句对使用的对象进行判空,比如:
public String getVal(ClassA obj) {
if (obj == null) {
return "unknown";
} else {
return obj.getValue();
}
}
但一旦对象的包含层次特别多时,条件语句的嵌套就会很复杂。而 Optional
类提供了一系列的方法可以通过链式编程的方式来简化代码,提供更便捷的形式。结合函数式编程来使用 Optional
,达到流式调用,则更加能够感受到它的强大之处。
Optional
本身其实是一个装有对象的容器,该对象可以存在,也可以为 null。对象的有无影响着其方法的调用模式。
常用函数
Optional
主要提供了三种构造函数:
Optional.empty()
:返回一个空的 Optional 对象。Optional.of(T value)
:内部调用一个私有的new Optional<>(value)
构造方法,对象保存value
的值,要求传入的value
不能为 null,否则就会报空指针异常。Optional.ofNullable(T value)
:根据传入value
值的不同来构造不同的实体,value
为 null 时,则返回 Optional.empty() 的结果;否则返回Optional.of(T value)
的结果。
除了这些构造方法外,Optional
还有一些常用的成员方法,如:
boolean isPresent()
:值为 null 则返回 false;否则返回 true。Optional<T> filter(Predicate<? super T> predicate)
:入参函数 predicate 进行条件判断返回 boolean 值,如果对象有值且满足条件则返回包含该值的 Optional,否则返回空 Optional 对象。Optional<U> map(Function<? super T, ? extends U> mapper)
:如果有值,则对其执行 mapper 函数得到返回值,该返回值作为 Optional 容器的对象进行返回,否则返回空 Optional 对象。Optional<U> flatMap(Function<? super T, Optional<? extends U>> mapper)
:如果有值,则执行 mapper 返回 Optional 类型的返回值,否则返回空 Optional 对象。void ifPresent(Consumer<? super T> consumer)
:如果有值,则对其执行 consumer 方法,否则不作处理。T get()
:如果 Optional 对象有值则将其返回,否则抛出 NoSuchElementException。T orElse(T other)
:如果有值则将其返回,否则返回指定的其他值。T orElseGet(Supplier<? extends T> other)
:与上述 orElse() 方法类似。区别在于 orElse() 传入与返回值相同类型的默认值,而 orElseGet() 传入一个 Supplier 方法,用于生成一个默认值。T orElseThrow(Supplier<? extends X> exceptionSupplier)
:如果有值则将其返回,否则抛出 supplier 方法创建的异常。
使用注意事项
我们在概述中提到过,Optional 的出现简化了我们对结果对象判空处理的代码实现。换个角度来说,Optional 作为一种结果返回类型,提供了对结果处理更为友好的解决方案。与传统冗余的条件语句相比,Optional 的优势在于,它能够通过链式调用对代码进行缩减,同时保持良好的代码可读性。从这个角度考虑,使用 Optional 需要判断当前场景是否能够被优化,避免滥用 Optional 的链式调用,因此上面说明的 Optional 常用函数在使用时需要注意到一些误区。
举个例子,本文开头的代码如果滥用 Optional 进行改造的话,可以得到下面的代码:
public String getVal(ClassA obj) {
Optional<Object> op = Optional.ofNullable(obj);
if (!op.isPresent()) {
return "unknown";
} else {
return op.get().getValue();
}
}
这样的改写操作其实跟源代码一样,只是用 isPresent()
方法替代了原来的 null 判断,而且代码反而变得不够整洁,显得冗余。一种正确的改写方式如下:
public String getVal(ClassA obj) {
return Optional.ofNullable(obj)
.map(b -> b.getValue())
.orElse("unknown");
}
这样子的链式调用才是正确使用 Optional 的方式,即减少了 if-else 语句的冗余代码,又保证了良好的代码可读性。上面的语句还可以用 getter 方法进一步优化:
public String getVal(ClassA obj) {
return Optional.ofNullable(obj)
.map(ClassA::getValue)
.orElse("unknown");
}
还有其他的一些 Optional 函数,在使用时注意不能只是单纯拿来替代传统代码的实现,也就是使用 Optional 经常需要避免一些误区:
- 避免使用
isPresent()
来检查对象是否为 null,这跟obj != null
没有区别。 - 避免使用
get()
来获取对象,因为使用前需要使用isPresent()
来判断对象是否为空,否则会抛出异常。所以应该使用orElse()
,orElseGet()
或者orElseThrow()
来获取结果。 - 不要将 null 赋值给 Optional,每当对返回结果不确定时,就可以使用 Optional 作为返回类型。
除此之外还需要注意的是,Optional 类是没有实现 Serializable 接口的,也就是说它不支持序列化,因此我们不能将 Optional 类的对象作为其他类的字段。如果需要序列化的对象包含 Optional 对象,那么在 Java 8 的 Jackson 库支持把 Optional 当成普通对象,即空对象被当作 null,有值的对象也会看作其值本身,丢失了 Optional 的属性。所以就像上面所说,Optional 主要还是作为一种工具,作为一种返回类型对返回结果的数据以一种更友好的方式进行处理。
Optional 经典实践
上面对 Optional 类中的函数都作了简要说明,但是在实践中不是每一个方法都会经常被使用到。接下来提供一些经典场景的演示代码,如果在实践中还有其他常见的情况,欢迎大家进行补充。
- 获取指定字段,返回非null结果
// 返回个人简介内容,没有则返回空字符串
String intro = Optional.ofNullable(person.getIntro()).orElse("");
// 返回学生列表,没有则返回空列表
// 1-融合版
if (data != null) {
List<Student> students1 = Optional.ofNullable(data.getStudents()).orElse(Collections.emptyList());
}
// 2-嵌套版
List<Student> students2 = Optional.ofNullable(Optional.ofNullable(data).orElse(new Data()).getStudents()).orElse(Collections.emptyList());
// 3-优雅版
List<Student> students3 = Optional.ofNullable(data).map(Data::getStudents).orElse(Collections.emptyList());
- 获取指定字段,不存在时通过工具方法生成赋值或抛出异常
// 补充缺失的uid字段
String uid = Optional.ofNullable(data.getUid()).orElseGet(() -> UUID.randomUUID().toString());
// uuid不能存在则抛出异常
String uuid = Optional.ofNullable(data.getUid()).orElseThrow(Exception::new);
- 进行条件筛选
// 列表大小小于5时返回空列表
List<Student> list = Optional.ofNullable(data).map(Data::getStudents).filter(s -> s.size() > 5).orElse(Collections.emptyList());
至于其他类似 of
,empty
,get
,isPresent
,这些方法,其功能比较单一,或者有太多需要注意的前提,只有在某些特殊情况才会用到,这里就不在作详细解释了。