一、简介
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()); }
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,它会返回一个 Optional,它所包含的 User 对象,其电子邮件为 “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()); }
这里对 Stream 的使用带来了其 filter()、map() 和 collect() 接口,以获取 List。