Java利用Optional解决空指针异常

 更新时间:2022年09月14日 09:46:13   作者:飞书技术  
这篇文章主要介绍了Java利用Optional解决空指针异常,Optional 类是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为空

背景介绍

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

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

到此这篇关于Java利用Optional解决空指针异常的文章就介绍到这了,更多相关Java Optional 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java基础之命名规范的详解

    Java基础之命名规范的详解

    这篇文章主要介绍了Java基础之命名规范的详解,文中有非常详细的代码示例,对正在学习Java基础的小伙伴们有很好地帮助,需要的朋友可以参考下
    2021-05-05
  • Java猴子吃桃问题

    Java猴子吃桃问题

    这篇文章主要介绍了Java猴子吃桃问题,采取逆向思维的方法,从后往前推断,需要的朋友可以参考下
    2017-02-02
  • 详解MyBatis-Puls中saveBatch批量添加慢的问题

    详解MyBatis-Puls中saveBatch批量添加慢的问题

    本文主要介绍了详解MyBatis-Puls中saveBatch批量添加慢的问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01
  • Java 数据结构深入理解ArrayList与顺序表

    Java 数据结构深入理解ArrayList与顺序表

    ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素。ArrayList 继承了 AbstractList ,并实现了 List 接口,顺序表是将元素顺序地存放在一块连续的存储区里,元素间的顺序关系由它们的存储顺序自然表示
    2022-04-04
  • Spring Cloud Stream消息驱动组件使用方法介绍

    Spring Cloud Stream消息驱动组件使用方法介绍

    Spring Cloud Stream 消息驱动组件帮助我们更快速,更方便,更友好的去构建消息驱动微服务的。当时定时任务和消息驱动的⼀个对比。消息驱动:基于消息机制做一些事情
    2022-09-09
  • Java-lambda表达式入门看这一篇就够了

    Java-lambda表达式入门看这一篇就够了

    lambda表达式最简单的作用就是用于简化创建匿名内部类对象,Lambda表达式是一个可传递的代码块,可以在以后执行一次或多次,下面通过本文给大家介绍Java-lambda表达式入门教程,感兴趣的朋友一起看看吧
    2021-05-05
  • mybatis中foreach嵌套if标签方式

    mybatis中foreach嵌套if标签方式

    这篇文章主要介绍了mybatis中foreach嵌套if标签方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • SpringBoot整合WebSocket实现聊天室流程全解

    SpringBoot整合WebSocket实现聊天室流程全解

    WebSocket协议是基于TCP的一种新的网络协议。本文将通过SpringBoot集成WebSocket实现简易聊天室,对大家的学习或者工作具有一定的参考学习价值,感兴趣的可以了解一下
    2023-01-01
  • Java内存溢出和内存泄露

    Java内存溢出和内存泄露

    这篇文章主要介绍了Java内存溢出和内存泄露的相关资料,需要的朋友可以参考下
    2017-10-10
  • 深入了解Java线程池的原理使用及性能优化

    深入了解Java线程池的原理使用及性能优化

    JAVA线程池是一种管理和复用线程资源的机制,可以提高程序的效率和响应速度。本文将介绍线程池的原理、使用方法和性能优化技巧,帮助读者深入了解和应用JAVA线程池
    2023-04-04

最新评论