Java8 - Optional 使用总结

325 阅读5分钟

Java8引入了全新的Optional类,主要用来处理空指针异常(NullPointerException)。从本质上说该类属于包含可选值的封装类(wrapper class),因此它既可以包含对象也可以仅仅为空。

举例说明:

在 Java 8 之前,凡涉及到访问对象方法或者对象属性的操作,无论数量多寡,都可能导致空指针异常:

String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();

为确保上面实例不出现空指针异常,需对每一个值进行显式的null检查

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();
            }
        }
    }
}

但是很多时候会忘记null的检查,即使没有忘记写出上面的代码也将很难维护

为了解决上述问题,JDK在Java8的时候加入了Optional,Optional的javadoc介绍如下

A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value.

这是一个可以包含或者不包含非 null 值的容器。如果值存在则 isPresent()方法会返回 true,调用 get() 方法会返回该对象

构造Optional

JDK提供了三个静态方法来构造一个Optional

Optional.of(T value)

该方法通过一个非null的value来构造一个Optional,返回的Optional包含了value这个值,对于该方法,传入的参数一定不能为null,否则会抛出NullPointerException

Optional.ofNullable(T value)

该方法和of的区别在于,传入的参数可以为null,与之前Optional javadoc提到的不为null有冲突,原因可以参考ofNullable方法的源码

public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}

该方法会对传入的参数进行null判断,如果为null,实际上返回的是Optional.empty

Optional.empty()

该方法用来构造一个空的Optional,底层代码实现如下

    /**
     * Common instance for {@code empty()}.
     */
    private static final Optional<?> EMPTY = new Optional<>();

    /**
     * If non-null, the value; if null, indicates no value is present
     */
    private final T value;

    /**
     * Constructs an empty instance.
     *
     * @implNote Generally only one empty instance, {@link Optional#EMPTY},
     * should exist per VM.
     */
    private Optional() {
        this.value = null;
    }

方法使用总结

前面JavaDoc提到,Optional的isPresent()用来判断是否包含值,get()方法用来获取Optional包含的值,需要注意在Optional.empty上调用get()方法将抛出NoSuchElementException异常

举例如下,完整代码在文章底部

Optional<User> user = Optional.ofNullable(getUserById(id));
if (user.isPresent()) {
    String username = user.get().getUsername();
    System.out.println("Username is: " + username); // 使用 username
}

和之前使用null的判断没啥区别,还需要使用Optional封装value,增加了代码里,实际上这不是使用Optional的正确姿势,下面将详细介绍Optional各个方法的使用

ifPresent

底层源码

public void ifPresent(Consumer<? super T> consumer) {
    if (value != null)
        consumer.accept(value);
}

上面获取用户名的例子可以重构成如下代码

Optional<User> user = Optional.ofNullable(getUserById(userId));
user.ifPresent(u -> System.out.println(u.getUsername()));

orElse

底层源码

public T orElse(T other) {
    return value != null ? value : other;
}

使用举例

User user1 = Optional
    .ofNullable(getUserById(nullUserId))
    .orElse(new User("Unknown", "Unknown", 99));
System.out.println(user1.toString());

orElseGet

orElseGet与orElse的区别在于,orElseGet方法传入的参数为一个Supplier接口的实现,当Optional有值的时候,返回值,没有值的时候,返回该Supplier的值

底层源码

public T orElseGet(Supplier<? extends T> other) {
    return value != null ? value : other.get();
}

使用举例

User user2 = Optional
    .ofNullable(getUserById(userId))
    .orElseGet(() -> new User("Unknown", "unknown", 88));
System.out.println(user2.toString());

orElseThrow

orElseThrow与orElse方法的区别,orElseThrow方法当Optional中有值的时候返回值,没有值的时候抛出异常,抛出的异常由传入的exceptionSupplier提供

底层源码

public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (value != null) {
        return value;
    } else {
        throw exceptionSupplier.get();
    }
}

使用举例

try {
    User user3 = Optional
        .ofNullable(getUserById(nullUserId))
        .orElseThrow(() -> new Exception("cannot found user of " + nullUserId));
    } catch (Exception e) {
        e.printStackTrace();
}

map

如果当前Optional为Optional.empty,则依旧返回Optional.empty;否则返回一个新的Optional,该Optional包含的是:函数mapper在以value作为输入时的输出值,可以多次使用map操作

底层源码

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));
    }
}

使用举例

String userName = Optional.ofNullable(getUserById(nullUserId))
    .map(u -> u.getUsername())
    .map(name -> name.toLowerCase())
    .orElse("Unknown");
System.out.println(userName);

flatMap

flatMap与map方法的区别在于,map方法参数中函数mapper输出的是值,然后map方法会使用Optional.ofNullable将其包装为Optional,而flatMap要求参数中的函数mapper输出的就是Optional

底层源码

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<String> userName1 = Optional.ofNullable(getUserById(userId))
    .flatMap(u -> Optional.of(u.getUsername()))
    .flatMap(name -> Optional.of(name.toLowerCase()));
System.out.println(userName1);

filter

filter方法接收一个Predicate来对Optional中包含的值进行过滤,如果包含的值满足条件,则还是返回这个Optional,否则返回Optional.empty

底层源码

public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent())
        return this;
    else
        return predicate.test(value) ? this : empty();
}

使用举例

Optional<String> userName2 = Optional.ofNullable(getUserById(userId))
    .filter(u -> u.getUsername() != "Jack")
    .map(u -> u.getUsername());
System.out.println(userName2);

完整代码

    public static void main(String[] args) {

        int nullUserId = 0;
        int userId = 666;
        Optional<User> user = Optional.ofNullable(getUserById(userId));
        user.ifPresent(u -> System.out.println(u.getUsername()));

        User tmpUser = new User();
        user.ifPresent(u -> tmpUser.setUsername(u.getUsername()));
        System.out.println(tmpUser.toString());

        user.ifPresent(u -> {int age = u.getAge();
            System.out.println(age);});

        User user1 = Optional
                .ofNullable(getUserById(nullUserId))
                .orElse(new User("Unknown", "Unknown", 99));
        System.out.println(user1.toString());

        User user2 = Optional
                .ofNullable(getUserById(userId))
                .orElseGet(() -> new User("Unknown", "unknown", 88));
        System.out.println(user2.toString());

        try {
            User user3 = Optional
                    .ofNullable(getUserById(nullUserId))
                    .orElseThrow(() -> new Exception("cannot found user of " + nullUserId));
        } catch (Exception e) {
            e.printStackTrace();
        }

        String userName = Optional.ofNullable(getUserById(nullUserId))
                .map(u -> u.getUsername())
                .map(name -> name.toLowerCase())
                .orElse("Unknown");
        System.out.println(userName);

        Optional<String> userName1 = Optional.ofNullable(getUserById(userId))
                .flatMap(u -> Optional.of(u.getUsername()))
                .flatMap(name -> Optional.of(name.toLowerCase()));
        System.out.println(userName1);

        Optional<String> userName2 = Optional.ofNullable(getUserById(userId))
                .filter(u -> u.getUsername() != "Jack")
                .map(u -> u.getUsername());
        System.out.println(userName2);
    }

    private static User getUserById(int userId) {
        if (666 == userId) {
            return new User("Jack", "Beijing", 18);
        } else {
            return null;
        }
    }

参考

Java8(5):使用 Optional 处理 null

了解、接受和利用Java中的Optional