还在用又臭又长的多层 if 判断空值?那我推荐你使用 Optional
2024-09-03 09:23 阅读(326)

版本


JDK 8


前言


Optional 是 Java 8 中引入的一个有趣类,用于表示一个值存在或不存在。它的设计目的是解决空指针异常(NullPointerException)问题,使得程序更加健壮、简洁。


先看一个小案例


大家看一下下面的代码是否存在问题?聪明的小伙伴也许都看出来了,代码没有进行判空检验,异常的场景会抛出 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();
            }
        }
    }
}

那么我们方法可以优化上面这段代码呢?答案当然是肯定的,它就是我们今天要介绍的主角 Java8 引入的 Optional,接下来就让我们一起看看 Optional 的魅力以及如何优化上文中又臭又长的代码。


Optional

Optional 创建


Optional 提供三种方式来创建 Optional 对象:

        // 创建包含值的 Optional 对象
        Optional<String> nonEmptyOptional = Optional.of("Hello");

        // 创建一个空的 Optional 对象
        Optional<String> emptyOptional = Optional.empty();

        // 创建包含可能为空的值的 Optional 对象
        Optional<String> nullableOptional = Optional.ofNullable(null);

Optional 常用方法


isPresent():检查值是否存在。

get():获取值,如果值不存在会抛出 NoSuchElementException 异常。

orElse(T other):获取值,如果值不存在则返回指定的默认值。

orElseGet(Supplier<? extends T> other):获取值,如果值不存在则返回由 Supplier 提供的默认值。

orElseThrow(Supplier<? extends X> exceptionSupplier):获取值,如果值不存在则抛出由 Supplier 提供的异常。

        // 检查值是否存在
        System.out.println("nonEmptyOptional is present: " + nonEmptyOptional.isPresent());
        System.out.println("emptyOptional is present: " + emptyOptional.isPresent());
        System.out.println("nullableOptional is present: " + nullableOptional.isPresent());

        // 获取值
        System.out.println("nonEmptyOptional value: " + nonEmptyOptional.get());
        
        // 值为空是返回指定异常
        nullableOptional.orElseThrow(() -> new IllegalStateException("field is not present"));

orElse(T other) 和 orElseGet(Supplier<? extends T> other)


两者都是在值不存在时返回默认值,但还是有一些差异:1、接受参数不同 2、某些场景写法中存在性能问题(注意点)


Optional 为 null

public class TestMain {

    public static void main(String[] args) {
        Optional<String> nullableOptional = Optional.ofNullable(null);
        System.out.println(nullableOptional.orElse(defaultStr()));
        System.out.println(nullableOptional.orElseGet(() -> {
            System.out.println("执行 defaultStr 方法 orElseGet");
            return "defaultStr";
        }));
    }

    public static String defaultStr() {
        System.out.println("执行 defaultStr 方法");
        return "defaultStr";
    }
}

// 输出
执行 defaultStr 方法
defaultStr
执行 defaultStr 方法 orElseGet
defaultStr

Optional 不为 null

public class TestMain {

    public static void main(String[] args) {
        Optional<String> nullableOptional = Optional.ofNullable("123");
        System.out.println(nullableOptional.orElse(defaultStr()));
        System.out.println(nullableOptional.orElseGet(() -> {
            System.out.println("执行 defaultStr 方法 orElseGet");
            return "defaultStr";
        }));
    }

    public static String defaultStr() {
        System.out.println("执行 defaultStr 方法");
        return "defaultStr";
    }
}

// 输出
执行 defaultStr 方法
123
123

对比两种情况,我们发现 orElse(T other) 无论 Optional  是否 null 都会执行传入的函数获取结果值,在一些高并发的场景会造成额外的性能浪费,应尽可能选择使用 orElseGet(Supplier<? extends T> other)。


map 和 flatMap


map 和 flatMap 可以将当前值传入到参数函数中,并返回一个 Optional 对象,两者唯一的区别在于 flatMap 不会再次包装,即传入函数返回值为 Optional 类型,具体可以参考下面的例子:

    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 {
            // flatMap 不会主动包装为 Optional
            return Objects.requireNonNull(mapper.apply(value));
        }
    }

示例:

@Builder
@Getter
class User implements Serializable {
    private String name;
    private Integer age;

    public Optional<Integer> getAge() {
        return Optional.ofNullable(age);
    }
}

public class TestMain {

    public static void main(String[] args) {
        User user = User.builder().name("ming").age(null).build();
        Optional.ofNullable(user).map(User::getName).orElse("unknown");
        Optional.ofNullable(user).flatMap(User::getAge).orElse(0);
    }
}

了解 map 和 flatMap 方法十分重要,因为这是我们后文实现链式调用的关键。

注:get 方法中一部分返回了 Optional 类型,一方面是为了演示,另一个方面可以通过这种方式可以让阅读我们代码的人明确的感知到这个字段的值可能为 null 你需要谨慎处理。


filter 过滤


Optional 类也提供了按条件过滤值的方法,filter() 接受一个 Predicate 参数,返回测试结果为 true 的值。如果测试结果为 false 或 value 为 null,会返回一个空的 Optional。

Optional.ofNullable(user).filter(t -> t.getName().contains("test")).orElse(null);

// 如果 name 包含 test 则会返回 user ,否则返回空的 `Optional`, .orElse(null)  返回 null。

如何优化文章开头的代码

核心点在于使用 Optional 实现链式调用,首先我们需要对 User 类做一些小小的改造。

@Builder
@Getter
class User implements Serializable {
    private String name;
    private Integer age;

    private Address address;

    public Optional<Integer> getAge() {
        return Optional.ofNullable(age);
    }

    public Address getAddress() {
        return address;
    }
}

class Address {
    private Country country;

    public Optional<Country> getCountry() {
        return Optional.ofNullable(country);
    }
}

class Country {
    private String isocode;

    public Optional<String> getCountry() {
        return Optional.ofNullable(isocode);
    }
}

public class TestMain {

    public static void main(String[] args) {
        User user = User.builder().name("ming").age(null).build();
        Optional.ofNullable(user).map(User::getAddress)
                .flatMap(Address::getCountry)
                .flatMap(Country::getIsocode)
                .orElse("unknown");
    }
}

通过使用 Optional 重构,我们代码的可读性和健壮性都有了很大的提升。


Java9 中的增强


Java 9 为 Optional 类添加了三个方法:or()、ifPresentOrElse() 和 stream()。

or():与 orElse() 和 orElseGet() 类似,它们都在对象为空的时候提供了替代情况。or() 的返回值是由 Supplier 参数产生的另一个 Optional 对象。

ifPresentOrElse():需要两个参数:一个 Consumer 和一个 Runnable。如果对象包含值,会执行 Consumer 的动作,否则运行 Runnable。

stream():通过把实例转换为 Stream 对象,让我们从广大的 Stream API 中受益。如果没有值,它会得到空的 Stream;有值的情况下,Stream 则会包含单一值。


总结


Optional 是 Java 8 中引入的一个有趣类,它的设计目的是解决空指针异常(NullPointerException)问题,我们应该好好掌握它,从而让我们在程序代码中更加优雅的处理空指针异常(NullPointerException)问题,使我们的代码具备更好的可读性以及更加健壮。


作者:Lorin洛林

链接:https://juejin.cn