Java Optional
飞书技术 人气:0背景介绍
Java 8 引入了一个十分实用的 Optional 类,它主要是为了解决空指针异常(NullPointerException)。当我们对对象的属性进行检查,判断它的值是否为期望的格式,最终却发现我们查看的并不是一个对象,而是一个空指针,它会立即抛出一个让人厌烦的 NullPointerException 异常。
本质上,Optional 类是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为空。
案例
从一个简单的用例开始。在 Java 8 之前,任何访问对象方法或属性的调用都可能导致 NullPointerException:
String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();
为了确保不触发异常,需要在访问每一个值之前对其进行明确地检查:
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 优化这些代码。
基本用法解析
Optional 的对象可能包含值,也可能为空。可以使用同名方法创建一个空的 Optional。
Optional<User> emptyOpt = Optional.empty(); emptyOpt.get();
此时访问 emptyOpt 变量的值会导致 NoSuchElementException。
还可以使用 of() 和 ofNullable() 方法创建包含值的 Optioanal 实例,区别在于如果将 null 当作参数传进去 of() 会报空指针异常,所以当对象可能存在或者不存在,应该使用 ofNullable()。
Optional<User> opt = Optional.of(user); Optional<User> opt = Optional.ofNullable(user);
从 Optional 实例中获得实际值对象的方法之一是使用 get() 方法。
String name = "John"; Optional<String> opt = Optional.ofNullable(name); assertEquals("John", opt.get());
这个方法会在值为 null 的时候抛出异常。要避免异常,需要首先用 isPresent() 方法验证是否有值。
User user = new User("zjh@gmail.com", "1234"); Optional<User> opt = Optional.ofNullable(user); assertTrue(opt.isPresent()); assertEquals(user.getEmail(), opt.get().getEmail());
该方法除了执行检查,还接受一个 Consumer(消费者) 参数,如果对象不是空的,就对执行传入的 Lambda 表达式。
opt.ifPresent( u -> assertEquals(user.getEmail(), u.getEmail()));
除 ifPresent() 方法外,Optional 类提供了 API 用以返回对象值,或者在对象为空的时候返回默认值。
可以使用的方法是 orElse() ,它的工作方式非常直接,如果有值则返回该值,否则返回传递给它的参数值。
User user = null; User user2 = new User("zjh@gmail.com", "1234"); User result = Optional.ofNullable(user).orElse(user2); assertEquals(user2.getEmail(), result.getEmail());
此时 user 对象为 null,result 返回 orElse() 参数 user2,如果对象初始值不是 null,那么默认值会被忽略。
第二个同类型的 API 是 orElseGet() —— 其行为略有不同。这个方法会在有值的时候返回值,如果没有值,它会执行作为参数传入的 Supplier(供应者) 函数式接口,并将返回其执行结果:
User result = Optional.ofNullable(user).orElseGet( () -> user2);
orElse() 和 orElseGet() 的不同之处在于当 ofNullable() 传入参数不为空时,orElse() 方法仍然创建了 User 对象。与之相反,orElseGet() 方法不创建 User 对象。
在执行较密集的调用时,比如调用 Web 服务或数据查询,这个差异会对性能产生重大影响。
除了 orElse() 和 orElseGet() 方法,Optional 还定义了 orElseThrow() API —— 它会在对象为空的时候抛出异常,而不是返回备选的值:
User result = Optional.ofNullable(user) .orElseThrow( () -> new IllegalArgumentException());
这里,如果 user 值为 null,会抛出 IllegalArgumentException。
这个方法让我们有更丰富的语义,可以决定抛出什么样的异常,而不总是抛出 NullPointerException。
转换与过滤
通过 map() 和 flatMap() 方法可以转换 Optional 的值。
User user = new User("zjh@gmail.com", "1234"); String email = Optional.ofNullable(user) .map(u -> u.getEmail()).orElse("default@gmail.com"); assertEquals(email, user.getEmail());
map() 对值应用(调用)作为参数的函数,然后将返回的值包装在 Optional 中。这就使对返回值进行链式调用的操作成为可能 —— 这里的下一环就是 orElse() 。
flatMap() 也需要函数作为参数,并对值调用这个函数,然后直接返回结果
除了转换值之外,Optional 类也提供了按条件“过滤”值的方法。
filter() 接受一个 Predicate 参数,返回测试结果为 true 的值。如果测试结果为 false,会返回一个空的 Optional。
User user = new User("zjh@gmail.com", "1234"); Optional<User> result = Optional.ofNullable(user) .filter(u -> u.getEmail() != null && u.getEmail().contains("@")); assertTrue(result.isPresent());
案例优化
使用 Optional 重写最早介绍的案例,删除 null 检查,替换为 Optional 的方法。
User user = new User("zjh@gmail.com", "1234"); String result = Optional.ofNullable(user) .map(u -> u.getAddress()) .map(a -> a.getCountry()) .map(c -> c.getIsocode()) .orElse("default"); assertEquals(result, "default");
源码解析
private Optional() { this.value = null; } private Optional(T value) { this.value = Objects.requireNonNull(value); }
可以看到源码中的构造函数全部都被私有化, 我们能够通过工厂方法的方式来获取 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); } public static<T> Optional<T> empty() { @SuppressWarnings("unchecked") Optional<T> t = (Optional<T>) EMPTY; return t; }
上面的方法是不允许传 null 值的, 下面的是可以的,下面的方法传了空值, 它会默认的帮你创建一个空的 Optional 包装类对象。
isPresent() 是判断这个包装类是否为空, get() 是获取到被包装的对象。isPresent() 还可以接受一个 Consumer(消费者) 参数,如果对象不是空的,就对执行传入的 Lambda 表达式
public boolean isPresent() { return value != null; } public void ifPresent(Consumer<? super T> consumer) { if (value != null) consumer.accept(value); } public T get() { if (value == null) { throw new NoSuchElementException("No value present"); } return value; }
以下三个方法大致上相似, 如果被包装类为空, 一个是直接返回一个新的被包装对象, 一个是通过函数式编程接口 Supplier 返回一个新的被包装类对象, 最后一个直接返回一个指定异常。
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(); } }
以下三个方法接受 Predicate 接口,实现转换与过滤操作。
public Optional<T> filter(Predicate<? super T> predicate) { Objects.requireNonNull(predicate); if (!isPresent()) return this; else return predicate.test(value) ? this : 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)); } } 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)); } }
总结
Optional 是 Java 语言的有益补充 —— 它旨在减少代码中的 NullPointerExceptions。
通过设计,自然的融入了 Java 8 函数式支持。
总的来说,这个简单而强大的类有助于创建简单、可读性更强、比对应程序错误更少的程序。
加载全部内容