从电影列表中找一部电影,找到了就返回对象,没找到怎么办?以前经常返回 null,调用方再判断。Java 8 的 Optional 可以把“有值”和“没有值”这两种结果放在返回类型里。

Stream 的 findFirst() 返回的就是 Optional。继续使用前面的 Movie 类,下面的片段分别放进 main 方法运行:

import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;

public class OptionalDemo {
    public static void main(String[] args) {
        List<Movie> movieList = Movie.samples();
        // 放入下面要运行的示例
    }
}

从查询结果开始

查找《喜剧之王》,找到后打印名称:

Optional<Movie> movie = movieList.stream()
        .filter(item -> "喜剧之王".equals(item.getName()))
        .findFirst();
movie.ifPresent(item -> System.out.println(item.getName()));

输出为 喜剧之王。如果没有匹配项,ifPresent 中的代码就不会执行。

也可以把查询结果转换成名称,再给空结果一个默认值:

String name = movieList.stream()
        .filter(item -> "不存在的电影".equals(item.getName()))
        .findFirst()
        .map(Movie::getName)
        .orElse("未找到");
System.out.println(name); // 未找到

map 只在有值时调用转换函数。结果为空时,后面的 orElse 返回默认值。

创建 Optional

常见的创建方式有三种:

Optional<String> present = Optional.of("hello");
Optional<String> empty = Optional.empty();
String text = null;
Optional<String> nullable = Optional.ofNullable(text);

System.out.println(present.isPresent());  // true
System.out.println(empty.isPresent());    // false
System.out.println(nullable.isPresent()); // false

of 要求参数非空,传入 null 会抛出 NullPointerException。ofNullable 可以接收 null,此时得到空 Optional。

空结果用 Optional.empty() 表示,它和 null 不一样:上面的空结果仍是一个 Optional 对象,只是里面没有值。

orElse 和 orElseGet

这两个方法都能提供默认值,但默认值的计算时机不同。用一个打印语句就能看出来:

import java.util.Optional;

public class DefaultValueDemo {
    static String defaultName() {
        System.out.println("计算默认名称");
        return "未命名";
    }

    public static void main(String[] args) {
        Optional<String> name = Optional.of("喜剧之王");

        System.out.println(name.orElse(defaultName()));
        System.out.println(name.orElseGet(() -> defaultName()));
    }
}

输出:

计算默认名称
喜剧之王
喜剧之王

调用 orElse(defaultName()) 前,Java 会先计算方法参数,所以即使已有值,defaultName() 仍然会执行。

orElseGet 接收一个 Supplier,只有 Optional 为空时才调用它。如果 name 为空,两种写法都会计算默认名称。

filter 与 flatMap

Optional 也有 filter。找到电影后,再判断评分是否不低于 8.0:

String name = movieList.stream()
        .filter(item -> "无极".equals(item.getName()))
        .findFirst()
        .filter(item -> item.getScore().compareTo(new BigDecimal("8.0")) >= 0)
        .map(Movie::getName)
        .orElse("没有符合条件的电影");
System.out.println(name);

《无极》能被找到,但不符合评分条件,所以最后输出 没有符合条件的电影。

如果转换函数本身已经返回 Optional,用 flatMap 可以避免套出两层:

Optional<Movie> movie = movieList.stream()
        .filter(item -> "喜剧之王".equals(item.getName()))
        .findFirst();

Optional<String> firstActor = movie.flatMap(
        item -> item.getActorList().stream().findFirst());
System.out.println(firstActor.orElse("没有演员信息")); // 周星驰

这里 findFirst() 已经返回 Optional<String>。如果用 map,得到的类型会是 Optional<Optional<String>>;flatMap 直接使用内部返回的 Optional。

取值时处理空结果

直接对空 Optional 调用 get() 会抛出 NoSuchElementException。如果查询不到就是错误,可以明确抛出异常:

Movie movie = movieList.stream()
        .filter(item -> "不存在的电影".equals(item.getName()))
        .findFirst()
        .orElseThrow(() -> new IllegalArgumentException("电影不存在"));

前面的 map、ifPresent 和默认值示例,处理的则是“没找到也没关系”的情况。

最后修改:2018 年 07 月 01 日
如果觉得我的文章对你有用,请随意赞赏