一、简介
Optional类是Java8为了解决null值判断问题,借鉴google guava类库的Optional类而引入的一个同名Optional类,使用Optional类可以避免显式的null值判断(null的防御性检查),避免null导致的NPE(NullPointerException)。
我们来看一段代码:
public static String getGender(Student student){
if(null == student){
return "Unkown";
}
return student.getGender();
}
这是一个获取学生性别的方法,方法入参为一个Student对象,为了防止student对象为null, 做了防御性检查:如果值为null,返回"Unkown"。再看使用Optional优化后的方法:
public static String getGender(Student student){
return Optional.ofNullable(student).map(u -> u.getGender()).orElse("Unkown");
}
可以看到,Optional类结合lambda表达式的使用能够让开发出的代码更简洁和优雅。
二、Optional对象的创建
我们看下Optional类的部分源码:
private static final Optional<?> EMPTY = new Optional<>();
private final T value;
private Optional() {
this.value = null;
}
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
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);
}
// 1、创建一个包装对象值为空的Optional对象
Optional<String> optStr = Optional.empty();
// 2、创建包装对象值非空的Optional对象
Optional<String> optStr1 = Optional.of("optional");
// 3、创建包装对象值允许为空的Optional对象
Optional<String> optStr2 = Optional.ofNullable(null);
三、Optional 类典型接口的使用
下面以一些典型场景为例,列出Optional API常用接口的用法,并附上相应代码。
1、get()方法
简单看下get()方法的源码:
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
可以看到,get()方法主要用于返回包装对象的实际值,但是如果包装对象值为null,会抛出NoSuchElementException异常。
2、isPresent()方法
isPresent()方法的源码:
public boolean isPresent() {
return value != null;
}
可以看到,isPresent()方法用于判断包装对象的值是否非空。下面我们来看一段糟糕的代码:
public static String getGender(Student student){
Optional<Student> stuOpt = Optional.ofNullable(student);
if(stuOpt.isPresent()){
return stuOpt.get().getGender();
}
return "Unkown";
}
这段代码实现的是第一章(简介)中的逻辑,但是这种用法不但没有减少null的防御性检查,而且增加了Optional包装的过程,违背了Optional设计的初衷,因此开发中要避免这种糟糕的使用。
public static String getGender(Student student){
Optional.ofNullable(student).map(student.getGender()).orElse("Unkown");
}
3、ifPresent()方法
ifPresent()方法的源码:
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
ifPresent()方法接受一个Consumer对象(消费函数),如果包装对象的值非空,运行Consumer对象的accept()方法。示例如下:
public static void printName(Student student){
Optional.ofNullable(student).ifPresent(u -> System.out.println("The student name is : " + u.getName()));
}
上述示例用于打印学生姓名,由于ifPresent()方法内部做了null值检查,调用前无需担心NPE问题。
4、filter()方法
filter()方法的源码:
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
public static void filterAge(Student student){
Optional.ofNullable(student).filter( u -> u.getAge() > 18).ifPresent(u -> System.out.println("The student age is more than 18."));
}
上述示例中,实现了年龄大于18的学生的筛选。
5、map()方法
map()方法的源码:
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));
}
}
public static Optional<Integer> getAge(Student student){
return Optional.ofNullable(student).map(u -> u.getAge());
}
上述代码中,先用ofNullable()方法构造一个Optional<Student>对象,然后用map()计算学生的年龄,返回Optional<Integer>对象(如果student为null, 返回map()方法返回一个空的Optinal对象)。
6、flatMap()方法
flatMap()方法的源码:
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));
}
}
public static Optional<Integer> getAge(Student student){
return Optional.ofNullable(student).flatMap(u -> Optional.ofNullable(u.getAge()));
}
7、orElse()方法
orElse()方法的源码:
public T orElse(T other) {
return value != null ? value : other;
}
orElse()方法功能比较简单,即如果包装对象值非空,返回包装对象值,否则返回入参other的值(默认值)。如代码:
public static String getGender(Student student){
return Optional.ofNullable(student).map(u -> u.getGender()).orElse("Unkown");
}
8、orElseGet()方法
orElseGet()方法的源码:
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
orElseGet()方法与orElse()方法类似,区别在于orElseGet()方法的入参为一个Supplier对象,用Supplier对象的get()方法的返回值作为默认值。如:
public static String getGender(Student student){
return Optional.ofNullable(student).map(u -> u.getGender()).orElseGet(() -> "Unkown");
}
9、orElseThrow()方法
orElseThrow()方法的源码:
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
public static String getGender1(Student student){
return Optional.ofNullable(student).map(u -> u.getGender()).orElseThrow(() -> new RuntimeException("Unkown"));
}
orElseThrow()方法适用于包装对象值为空时需要抛出特定异常的场景。
四、Optional 类的链式方法
为了更充分的使用 Optional,你可以链接组合其大部分方法,因为它们都返回相同类似的Optional包装对象。
public class User {
private Address address;
public Optional<Address> getAddress() {
return Optional.ofNullable(address);
}
// ...
}
public class Address {
private Country country;
public Optional<Country> getCountry() {
return Optional.ofNullable(country);
}
// ...
}
其嵌套结构如下所示:
#JDK8以前的写法
if (user != null) {
Address address = user.getAddress();
if (address != null) {
Country country = address.getCountry();
if (country != null) {
String isocode = country.getIsocode();
if (isocode != null) {
isocode = isocode.toUpperCase();
}
}
}
}
#Optional改造写法
String result = Optional.ofNullable(user)
.flatMap(u -> u.getAddress())
.flatMap(a -> a.getCountry())
.map(c -> c.getIsocode())
.orElse("default");
#再优化一下
String result = Optional.ofNullable(user)
.flatMap(User::getAddress)
.flatMap(Address::getCountry)
.map(Country::getIsocode)
.orElse("default");
总结一下:
五、Java 9 增强
我们介绍了 Java 8 的特性,Java 9 为 Optional 类添加了三个方法:or()、ifPresentOrElse() 和 stream()。
1、or() 方法
or()函数源码如下:
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier) {
Objects.requireNonNull(supplier);
if (isPresent()) {
return this;
} else {
@SuppressWarnings("unchecked")
Optional<T> r = (Optional<T>) supplier.get();
return Objects.requireNonNull(r);
}
}
or() 方法与 orElse() 和 orElseGet() 类似,它们都在对象为空的时候提供了替代情况。or() 的返回值是由 Supplier 参数产生的另一个 Optional 对象。
如果对象包含值,则 Lambda 表达式不会执行:
public void whenEmptyOptional_thenGetValueFromOr() {
User result = Optional.ofNullable(user)
.or( () -> Optional.of(new User("default","1234"))).get();
assertEquals(result.getEmail(), "default");
}
上面的示例中,如果 user 变量是 null,它会返回一个 对象,其电子邮件为 “default”。
2、ifPresentOrElse() 方法
ifPresentOrElse() 方法需要两个参数:一个 Consumer 和一个 Runnable。如果对象包含值,会执行 Consumer 的动作,否则运行 Runnable。源码如下:
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
if (value != null) {
action.accept(value);
} else {
emptyAction.run();
}
}
如果你想在有值的时候执行某个动作,或者只是跟踪是否定义了某个值,那么这个方法非常有用:
Optional.ofNullable(user).ifPresentOrElse( u -> logger.info("User is:" + u.getEmail()),() -> logger.info("User not found"));
3、stream()
最后介绍的是新的 stream() 方法,它通过把实例转换为 Stream 对象,让你从广大的 Stream API 中受益。如果没有值,它会得到空的 Stream;有值的情况下,Stream 则会包含单一值。源码如下:
public Stream<T> stream() {
if (!isPresent()) {
return Stream.empty();
} else {
return Stream.of(value);
}
}
我们来看一个把 Optional 处理成 Stream 的例子:
public void whenGetStream_thenOk() {
User user = new User("john@gmail.com", "1234");
List<String> emails = Optional.ofNullable(user)
.stream()
.filter(u -> u.getEmail() != null && u.getEmail().contains("@"))
.map( u -> u.getEmail())
.collect(Collectors.toList());
assertTrue(emails.size() == 1);
assertEquals(emails.get(0), user.getEmail());
}
和 collect() 接口,以获取 List。