• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

【java】利用Optional解决空指针异常

武飞扬头像
juejin
帮助61

背景介绍

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 函数式支持。

总的来说,这个简单而强大的类有助于创建简单、可读性更强、比对应程序错误更少的程序。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/taneiii
系列文章
更多 icon
同类精品
更多 icon
继续加载