Use Optional to solve null pointer exception

Background introduction

Java 8 introduced a very useful Optional class, which is mainly to solve the NullPointerException (NullPointerException). When we check an object's property to see if its value is in the expected format, only to find that we're not looking at an object, but a null pointer, which immediately throws an annoying NullPointerException.

Essentially, the Optional class is a wrapper class that contains optional values, which means that the Optional class can either contain objects or be empty.

case

Start with a simple use case. Prior to Java 8, any call to access an object method or property could result in a NullPointerException:

String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();
复制代码

To ensure that no exceptions are raised, each value needs to be checked explicitly before accessing it:

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();
            }
        }
    }
}
复制代码

To simplify this process, we use Optional to optimize this code.

Basic usage analysis

An Optional object may contain a value, or it may be empty. An empty Optional can be created using the method of the same name.

Optional<User> emptyOpt = Optional.empty();
emptyOpt.get();
复制代码

Accessing the value of the emptyOpt variable at this point results in a NoSuchElementException.

You can also use the of() and ofNullable() methods to create an Optional instance containing a value. The difference is that if you pass null as a parameter to of() , a null pointer exception will be reported, so when the object may or may not exist, you should use ofNullable( ).

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

One of the ways to get the actual value object from an Optional instance is to use the get() method.

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

This method will throw an exception if the value is null. To avoid the exception, you need to first verify that there is a value with the isPresent() method.

User user = new User("[email protected]", "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("[email protected]", "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("[email protected]", "1234");
String email = Optional.ofNullable(user)
    .map(u -> u.getEmail()).orElse("[email protected]");
assertEquals(email, user.getEmail());
复制代码

map() 对值应用(调用)作为参数的函数,然后将返回的值包装在 Optional 中。这就使对返回值进行链式调用的操作成为可能 —— 这里的下一环就是 orElse()

flatMap() 也需要函数作为参数,并对值调用这个函数,然后直接返回结果

除了转换值之外,Optional 类也提供了按条件“过滤”值的方法。

filter() 接受一个 Predicate 参数,返回测试结果为 true 的值。如果测试结果为 false,会返回一个空的 Optional。

User user = new User("[email protected]", "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("[email protected]", "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 函数式支持。

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

加入我们

我们来自字节跳动飞书商业应用研发部(Lark Business Applications),目前我们在北京、深圳、上海、武汉、杭州、成都、广州、三亚都设立了办公区域。我们关注的产品领域主要在企业经验管理软件上,包括飞书 OKR、飞书绩效、飞书招聘、飞书人事等 HCM 领域系统,也包括飞书审批、OA、法务、财务、采购、差旅与报销等系统。欢迎各位加入我们。

扫码发现职位&投递简历

官网投递:job.toutiao.com/s/FyL7DRg

Guess you like

Origin juejin.im/post/7142813551210528776