从电影列表中找一部电影,找到了就返回对象,没找到怎么办?以前经常返回 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()); // falseof 要求参数非空,传入 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 和默认值示例,处理的则是“没找到也没关系”的情况。