Java8-Optional

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

参考

使用 Optional 处理 null
了解、接受和利用Java中的Optional

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 关于java8 Optional 文档版本:v1.0版本 和C/C++不一样,java从一开始就尝试将指针彻底的包...
    比轩阅读 3,827评论 1 22
  • Optional的学习与实战 整片文章大部分内容来自java8实战这本书,我在这里也是将自己的学习过程记录下来,并...
    Java大宝宝阅读 2,966评论 2 0
  • 根据Oracle文档,Optional是一个容器对象,可以包含也可以不包含非null值。Optional在Java...
    MadPecker阅读 10,788评论 1 8
  • 厌倦了空指针异常? 考虑使用Java SE 8的Optional!使代码更具可读性并使得免受空指针异常的影响。有人...
    bern85阅读 457评论 0 2
  • Optional java8添加的容器对象,在一些场景下避免使用null检查而设定的类,尽可能避免的NullPoi...
    风雨兼程_ad4d阅读 770评论 0 5