• 利用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 emptyOpt = Optional.empty();
    emptyOpt.get();
    复制代码

    此时访问 emptyOpt 变量的值会导致 NoSuchElementException

    还可以使用 of() 和 ofNullable() 方法创建包含值的 Optioanal 实例,区别在于如果将 null 当作参数传进去 of() 会报空指针异常,所以当对象可能存在或者不存在,应该使用 ofNullable()。

    Optional opt = Optional.of(user);
    Optional opt = Optional.ofNullable(user);
    复制代码

    从 Optional 实例中获得实际值对象的方法之一是使用 get() 方法。

    String name = "John";
    Optional opt = Optional.ofNullable(name);
    assertEquals("John", opt.get());
    复制代码

    这个方法会在值为 null 的时候抛出异常。要避免异常,需要首先用 isPresent() 方法验证是否有值。

    User user = new User("zjh@gmail.com", "1234");
    Optional 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 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  Optional of(T value) {
        return new Optional<>(value);
    }
    
    public static  Optional ofNullable(T value) {
        return value == null ? empty() : of(value);
    }
    
    public static Optional empty() {
        @SuppressWarnings("unchecked")
        Optional t = (Optional) EMPTY;
        return t;
    }
    复制代码

    上面的方法是不允许传 null 值的, 下面的是可以的,下面的方法传了空值, 它会默认的帮你创建一个空的 Optional 包装类对象。

    isPresent() 是判断这个包装类是否为空, get() 是获取到被包装的对象。 isPresent() 还可以接受一个 Consumer(消费者) 参数,如果对象不是空的,就对执行传入的 Lambda 表达式

    public boolean isPresent() {
        return value != null;
    }
    
    public void ifPresent(Consumer 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 other) {
        return value != null ? value : other.get();
    }
    
    public  T orElseThrow(Supplier exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }
    复制代码

    以下三个方法接受 Predicate 接口,实现转换与过滤操作。

    public Optional filter(Predicate predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent())
            return this;
        else
            return predicate.test(value) ? this : empty();
    }
    
    public Optional map(Function mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }
    
    public Optional flatMap(Function> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }
    复制代码

    总结

    Optional 是 Java 语言的有益补充 —— 它旨在减少代码中的 NullPointerExceptions 。

    通过设计,自然的融入了 Java 8 函数式支持。

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

  • 相关阅读:
    编程语言发展史:高级语言的兴起
    sql中on、where、having的区别
    springboot+vue实现登录案例(附VUE整个项目代码)
    工业和能源1994-2019年省级面板数据
    GIS工具maptalks开发手册(三)03——官网示例之添加图层和移除图层
    Linux0.11内核源码解析01
    C语言 typedef
    互联网医院系统源码:预约问诊小程序的开发方案详解
    selectTree单选iview+vue
    第四章:存储子系统 [计算机组成原理笔记](自用)
  • 原文地址:https://blog.csdn.net/AS011x/article/details/126848254