Java 8 特性 Optional 详解

原创 java

Java 8 有个 Optional 类供我们使用,该类可以尽可能地防止出现空指针异常(NPE)。

一、基础铺垫

我们都知道 JDK 8 最重要的新特性是 Lambda 表达式,这个可以让我们简化非常多的代码编写,不知道大家会使用了没有。这里我简单跟大家来回顾一下。

1.1 Lambda 简化代码例子

下面就以几个例子来看看 Lambda 表达式是怎么简化我们代码的编写的。

首先我们来看看创建线程:

public static void main(String[] args) {
    // 用匿名内部类的方式来创建线程
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("DO SOMETHING!");
        }
    });

    // 使用 Lambda 来创建线程
    new Thread(() -> System.out.println("DO SOMETHING!"));
}

再来看看遍历 Map 集合:

public static void main(String[] args) {
    Map<String, String> hashMap = new HashMap<>();
    hashMap.put("key1", "value1");
    hashMap.put("key2", "value2");

    // 使用增强 for 的方式来遍历 hashMap
    for (Map.Entry<String, String> entry : hashMap.entrySet()) {
        System.out.println(entry.getKey() + ":" + entry.getValue());
    }

    // 使用 Lambda 表达式的方式来遍历 hashMap
    hashMap.forEach((s, s2) -> System.out.println(s + ":" + s2));
}

在 List 中删除某个元素

public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("a");
    list.add("b");
    list.add("c");
    list.add("d");
    
    // 传统的方式删除 "c" 的元素
    ListIterator<String> iterator = list.listIterator();
    while (iterator.hasNext()) {
        if ("c".equals(iterator.next())) {
            iterator.remove();
        }
    }

    // Lambda 方式删除 "c" 的元素
    list.removeIf(s -> "c".equals(s));
    
    // 使用 Lambda 遍历 List 集合
    list.forEach(s -> System.out.println(s));
}

从上面的例子我们可以看出,Lambda 表达式的确是可以帮我们简化代码的。

1.2 函数式接口

使用 Lambda 表达式,其实都是建立在函数式接口上的。我们看看上面的代码的接口:

创建多线程的 Runnable 接口:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

遍历 HashMap 的 BiConsumer 接口:

@FunctionalInterface
public interface BiConsumer<T, U> {
    void accept(T t, U u);
    default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
        Objects.requireNonNull(after);
        return (l, r) -> {
            accept(l, r);
            after.accept(l, r);
        };
    }
}

在 List 中删除元素的 Predicate 接口:

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

函数式接口的特点:由 @FunctionalInterface 注解标识,接口有且仅有一个抽象方法!

1.3 Lambda 简单讲解

或许我们一开始看到 Lambda 的时候,发现 Lambda 表达式的语法有点奇葩,甚至有点看不懂。没事,这里给大家用图的形式画一画:

java-lambda-body.jpg

以 Runnable 接口来举例:

java-lambda-body-demo.jpg

再不济,我们在用 IDE 的时候,可以提示出 Lambda 表达式的语法的,这样可以帮我们快速上手 Lambda 表达式:

java-lambda-idea-tip.jpg

说白了,我们使用 Lambda 表达式的架子是这样的 ()->{},具体的时候看看函数式接口的抽象方法要求就可以了,再不济就使用 IDE 智能提示。

1.4 泛型回顾

比如说 public<U> Optional<U> map(Function<? super T, ? extends U> mapper) 这个声明,你看懂了吗?

// 接口
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

在泛型的上限和下限中有一个原则:PECS(Producer Extends Consumer Super)

  • 带有子类限定的可以从泛型读取【也就是--->(? extend T)】-------->Producer Extends
  • 带有超类限定的可以从泛型写入【也就是--->(? super T)】-------->Consumer Super

解析:传入的参数是泛型 T 或者其父类,返回值是 U 或其子类。

二、Optional 类

一句话介绍 Optional 类:使用 JDK8 的 Optional 类来防止 NPE(空指针异常)问题。

接下来我们看看文档是怎么说的:

A container object which may or may not contain a non-null value.Additional methods that depend on the presence or absence of a contained value are provided

它是一个容器,装载着非 null 元素(或者没有装载元素),提供了一系列的方法供我们判断该容器里的对象是否存在(以及后续的操作)。

Optional 类的方法结构图:

java-optional-struct.jpg

2.1 创建 Optional 容器

先来看看 Optional 的属性以及创建 Optional 容器的方法:

// 1、创建出一个 Optional 容器,容器里边并没有装载着对象
private static final Optional<?> EMPTY = new Optional<>();

// 2、代表着容器中的对象
private final T value;

// 3、私有构造方法
private Optional() {
    this.value = null;
}

// 4、得到一个 Optional 容器,Optional 没有装载着对象
public static<T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
}

// 5、私有构造方法(带参数),参数就是具体的要装载的对象,如果传进来的对象为 null,抛出异常
private Optional(T value) {
    this.value = Objects.requireNonNull(value);
}

// 5.1、如果传进来的对象为 null,抛出异常
public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}


// 6、创建出 Optional 容器,并将对象(value)装载到 Optional 容器中。
// 传入的value如果为null,抛出异常(调用的是Optional(T value)方法)
public static <T> Optional<T> of(T value) {
    return new Optional<>(value);
}

// 创建出 Optional 容器,并将对象(value)装载到 Optional 容器中。
// 传入的value可以为null,如果为null,返回一个没有装载对象的Optional对象
public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}

所以可以得出创建 Optional 容器有两种方式:

  • 调用 ofNullable() 方法,传入的对象可以为 null
  • 调用 of() 方法,传入的对象不可以为 null,否则抛出 NullPointerException

下面我们简单就可以看看用法了:

有一个 User 对象,

import lombok.Data;
@Data
public class User {
    private Integer id;
    private String name;
    private Short age;
}

测试:

public static void main(String[] args) {
    User user = new User();
    User user1 = null;

    // 传递进去的对象不可以为 null,如果为 null 则抛出异常
    Optional<User> op1 = Optional.of(user1);

    // 传递进去的对象可以为 null,如果为 null 则返回一个没有装载对象的 Optional 容器
    Optional<User> op2 = Optional.ofNullable(user);
}

2.2 Optional 容器简单的方法

// 得到容器中的对象,如果为null就抛出异常
public T get() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}

// 判断容器中的对象是否为null
public boolean isPresent() {
    return value != null;
}

// 如果容器中的对象存在,则返回。否则返回传递进来的参数
public T orElse(T other) {
    return value != null ? value : other;
}

这三个方法是 Optional 类比较常用的方法,并且是最简单的。(因为参数不是函数式接口)

下面我们继续看看用法:

public static void main(String[] args) {
    User user = new User();
    User user1 = null;

    Optional<User> op1 = Optional.ofNullable(user);
    System.out.println(op1.isPresent());
    System.out.println(op1.get());
    System.out.println(op1.orElse(user1));

}

结果很明显,因为我们的 user 是不为 null 的:

r1.jpg

我们调换一下顺序看看:

public static void main(String[] args) {
    User user = new User();
    User user1 = null;

    Optional<User> op1 = Optional.ofNullable(user1);
    System.out.println(op1.isPresent());
    System.out.println(op1.orElse(user));
    System.out.println(op1.get());

}

r2.jpg

2.3 Optional 容器进阶用法

当然了,我们到目前为止看起来 Optional 类好像就这么一回事了,这样代码写起来还不如我自己判断 null 呢... 我们对比一下。

传统的方式:

public String tradition(User user) {
    if (user != null) {
        return user.getName();
    } else {
        return "Unknown";
    }
}

使用 Optional 方式:

public String present(User user) {
    Optional<User> optional = Optional.ofNullable(user);
    if (optional.isPresent()) {
        return optional.get().getName();
    } else {
        return "Unknown";
    }
}

我们可以发现,手动判断是否为 null 好像还更方便简洁一点呢。

所以,我们带函数式接口的方法登场了!

2.3.1 ifPresent 方法

首先来看看 ifPresent(Consumer<? super T> consumer) 方法。

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

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

如果容器中的对象存在,则调用 accept 方法,比如说:

public static void main(String[] args) {

    User user = new User();
    user.setName("Java3y");
    test(user);
}

public static void test(User user) {

    Optional<User> optional = Optional.ofNullable(user);

    // 如果存在user,则打印user的name
    optional.ifPresent((value) -> System.out.println(value.getName()));

    // 旧写法
    if (user != null) {
        System.out.println(user.getName());
    }
}

2.3.2 orElseGet 和 orElseThrow 方法

源码:

// 如果对象存在,则直接返回,否则返回由Supplier接口的实现用来生成默认值
public T orElseGet(Supplier<? extends T> other) {
    return value != null ? value : other.get();
}

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

// 如果存在,则返回。否则抛出supplier接口创建的异常
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (value != null) {
        return value;
    } else {
        throw exceptionSupplier.get();
    }
}

例子:

public static void main(String[] args) {
    User user = new User();
    user.setName("Java3y");
    test(user);
}

public static void test(User user) {
    Optional<User> optional = Optional.ofNullable(user);

    // 如果存在user,则直接返回,否则创建出一个新的User对象
    User user1 = optional.orElseGet(() -> new User());
    
    // 旧写法
    if (user != null) {
        user = new User();
    }
}

总的来说跟我们上面所讲的 orElse() 差不多,只不过它可以通过 Supplier 接口的实现来生成默认值。

2.3.3 filter 方法

源码:

// 如果容器中的对象存在,并且符合过滤条件,返回装载对象的Optional容器,否则返回一个空的Optional容器
public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent())
        return this;
    else
        return predicate.test(value) ? this : empty();
}

// 接口
@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);
}

返回 Optional 对象我们就可以实现链式调用了!

例子:

public static void test(User user) {
    Optional<User> optional = Optional.ofNullable(user);

    // 如果容器中的对象存在,并且符合过滤条件,返回装载对象的Optional容器,否则返回一个空的Optional容器
    optional.filter((value) -> "Java3y".equals(value.getName()));
}

2.3.4 map 方法

源码:

// 如果容器的对象存在,则对其执行调用mapping函数得到返回值。然后创建包含mapping返回值的Optional,否则返回空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));
    }
}

// 接口
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

例子:

public static void test(User user) {

    Optional<User> optional = Optional.ofNullable(user);

    // 如果容器的对象存在,则对其执行调用mapping函数得到返回值。然后创建包含mapping返回值的Optional,否则返回空Optional。
    optional.map(user1 -> user1.getName()).orElse("Unknown");
}

// 上面一句代码对应着最开始的老写法:

public String tradition(User user) {
    if (user != null) {
        return user.getName();
    }else{
        return "Unknown";
    }
}

2.3.5 flatMap 方法

源码:

// flatMap方法与map方法类似,区别在于apply函数的返回值不同。
// map方法的apply函数返回值是? extends U,而flatMap方法的apply函数返回值必须是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 的魅力。

public static void main(String[] args) {
    User user = new User();
    user.setName("Java3y");
    System.out.println(test(user));
}

// 以前的代码v1
public static String test2(User user) {
    if (user != null) {
        String name = user.getName();
        if (name != null) {
            return name.toUpperCase();
        } else {
            return null;
        }
    } else {
        return null;
    }
}

// 以前的代码v2
public static String test3(User user) {
    if (user != null && user.getName() != null) {
        return user.getName().toUpperCase();
    } else {
        return null;
    }
}

// 现在的代码
public static String test(User user) {
    return Optional.ofNullable(user)
            .map(user1 -> user1.getName())
            .map(s -> s.toUpperCase()).orElse(null);
}

Optional 总结:

filtermapflatMap 一个函数,函数的参数拿到的值一定不是 null。所以我们通过 filter,map 和 flatMap 之类的函数可以将其安全的进行变换,最后通过 orElse 系列,getisPresentifPresent 将其中的值提取出来。

其实吧,用 Optional 类也没有简化很多的代码,只是把 NullPointerException 异常通过各种方法隐藏起来(包装了一层)。通过 Lambda 表达式可以让我们处理起来更加"优雅"一些。

如果觉得这对你有用,请随意赞赏,给与作者支持
评论 1
最新评论
#1 楼 斯巴达克斯 2018-12-08

Java 的 Optional 确实是要和 lambda 一起使用才能显示出实用和优雅。