目录
一. 前言
根据 Oracle 文档,Optional 是一个容器对象,可以包含也可以不包含非 null 值。Optional 在Java 8中引入,目的是解决 NullPointerException 的问题。本质上,Optional 是一个包装器类,其中包含对其他对象的引用。在这种情况下,对象只是指向内存位置的指针,并且也可以指向任何内容。从其他角度看,Optional 提供一种类型级解决方案来表示可选值而不是空引用。
二. Optional 用法
2.1. 概要
Optional 类的 Javadoc 描述如下:这是一个可以为 null 的容器对象。如果值存在则 isPresent()方法会返回 true,调用 get() 方法会返回该对象。如果值不存在则 isPresent() 方法会返回 false,调用 get() 方法会 NPE。
| 方法 | 描述 |
|---|---|
| empy() | 创建一个空的 Optional 对象 |
| of(T) | 把指定的值封装为 Optional 对象,如果指定的值为 null,则抛出 NullPointerException |
| ofNullable(T) | 把指定的值封装为 Optional 对象,如果指定的值为 null,则创建一个空的 Optional 对象 |
| get() | 如果创建的 Optional 中有值存在,则返回此值,否则抛出 NoSuchElementException |
| isPresent() | 如果创建的 Optional 中的值存在,返回 true,否则返回 false |
| ifPresent() | 如果创建的 Optional 中的值存在,则执行该方法的调用,否则什么也不做 |
| filter() | 如果创建的 Optional 中的值满足 filter() 中的条件,则返回包含该值的 Optional 对象,否则返回一个空的 Optional 对象 |
| map() | 如果创建的 Optional 中的值存在,对该值执行提供的 Function 函数调用 |
| flatMap() | 如果创建的 Optional 中的值存在,就对该值执行提供的 Function 函数调用,返回一个 Optional 类型的值,否则就返回一个空的 Optional 对象 |
| orElse(T) | 如果创建的 Optional 中有值存在,则返回此值,否则返回一个默认值 |
| orElseGet() | 如果创建的 Optional 中有值存在,则返回此值,否则返回一个由 Supplier 接口生成的值 |
| orElseThrow | 如果创建的 Optional 中有值存在,则返回此值,否则抛出一个由指定的 Supplier 接口生成的异常 |
2.2. 创建 Optional 对象
Optional.empty():返回一个空的 Optional 实例,Optional 的值不存在。如果对象为空,请避免与 Option.empty() 返回的实例的值比较 。因为不能保证它是一个单例,反之,应该使用isPresent()。
Optional.of(T value):创建一个 Optional 实例,该方法为静态方法,所以需要一个非 null 参数,也就是 value 必须非空;如果 value 为空,则抛出 NullPointerException。
Optional.ofNullable(T value):创建一个包含可能为 null 的值的 Optional 对象。
源码如下:
private static final Optional<?> EMPTY = new Optional<>();
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
示例如下:
public void testConstructor() {
// 1、创建一个包装对象值为空的Optional对象
Optional<String> optStr = Optional.empty();
// 2、创建包装对象值非空的Optional对象
Optional<String> optStr1 = Optional.of("optional");
// 3、创建包装对象值允许为空的Optional对象
Optional<String> optStr2 = Optional.ofNullable(null);
}
2.3. 获取 Optional 对象的值
get():获取 Optional 对象中的值。如果 Optional 对象为空,则抛出 NoSuchElementException 异常。
源码如下:
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
示例如下:
public void testGet() {
Optional<String> optional = Optional.of("thinkwon");
Optional<String> optional1 = Optional.ofNullable(null);
System.out.println(optional.get());
System.out.println(optional1.get());
}
// 运行结果:
thinkwon
java.util.NoSuchElementException: No value present
2.4. 检查 Optional 对象是否为空
isPresent():判断 value 是否存在,不为 NULL 返回 true,如果为 NULL 则返回 false。
源码如下:
public boolean isPresent() {
return value != null;
}
示例如下:
public void testIsPresent() {
Optional<String> optional = Optional.of("thinkwon");
Optional<String> optional1 = Optional.ofNullable(null);
System.out.println(optional.isPresent());
System.out.println(optional1.isPresent());
}
// 运行结果:
true
false
注意:下面例子的用法不但没有减少 null 的防御性检查,而且增加了 Optional 包装的过程,违背了 Optional 设计的初衷,因此开发中要避免这种糟糕的使用:
public void testIsPresent() {
Optional<String> optional = Optional.ofNullable(null);
if (optional.isPresent()) {
System.out.println(optional.get());
}
}
试想一下如果先用 isPresent() 方法获得是否存在,然后决定是否调用 get() 方法,那和使用 if else 判断并无二致。
2.5. 安全访问 Optional 对象的值
ifPresent(Consumer<? super T> consumer):接受一个 Consumer 对象(消费函数),如果包装对象的值非空,运行 Consumer 对象的 accept() 方法。
源码如下:
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
示例如下:
public void testIfPresent() {
Optional<String> optional = Optional.of("thinkwon");
optional.ifPresent(s -> System.out.println("the String is " + s));
}
// 运行结果:
the String is thinkwon
2.6. 处理过滤操作
filter(Predicate<? super T> predicate):对 Optional 对象中的值进行过滤操作,返回一个满足条件的 Optional 对象。
源码如下:
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
示例如下:
public void testFilter() {
Optional.of("thinkwon").filter(s -> s.length() > 2)
.ifPresent(s -> System.out.println("The length of String is greater than 2 and String is " + s));
}
// 运行结果:
The length of String is greater than 2 and String is thinkwon
2.7. 处理转换操作
map(Function<? super T, ? extends U> mapper):将 Optional 中的包装对象用 Function 函数进行运算,并包装成新的 Optional 对象(包装对象的类型可能改变)。
flatMap(Function<? super T, Optional<U>> mapper):跟 map() 方法不同的是,入参 Function 函数的返回值类型为 Optional<U> 类型,而不是 U 类型,这样 flatMap() 能将一个二维的 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));
}
}
public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent()) {
return empty();
} else {
@SuppressWarnings("unchecked")
Optional<U> r = (Optional<U>) mapper.apply(value);
return Objects.requireNonNull(r);
}
}
示例如下:
public void testMap() {
Optional<String> optional = Optional.of("thinkwon").map(s -> s.toUpperCase());
System.out.println(optional.get());
Optional<String> optional2 = Optional.of("thinkwon").flatMap(s -> Optional.ofNullable(s.toUpperCase()));
System.out.println(optional2.get());
}
// 运行结果:
THINKWON
THINKWON
2.8. 使用默认值
orElse():即如果包装对象值非空,返回包装对象值,否则返回入参 other 的值(默认值)。
orElseGet():与 orElse() 方法类似,区别在于 orElseGet() 方法的入参为一个 Supplier 对象,用Supplier 对象的 get() 方法的返回值作为默认值。
orElseThrow():与orElseGet()方法非常相似,入参都是 Supplier 对象,只不过 orElseThrow() 的Supplier 对象必须返回一个 Throwable 异常,并在 orElseThrow() 中将异常抛出,orElseThrow()方法适用于包装对象值为空时需要抛出特定异常的场景。
源码如下:
public T orElse(T other) {
return value != null ? value : other;
}
public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
}
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
示例如下:
public void testOrElse() {
String unkown = (String) Optional.ofNullable(null).orElse("unkown");
System.out.println(unkown);
String unkown2 = (String) Optional.ofNullable(null).orElseGet(() -> "unkown");
System.out.println(unkown2);
Optional.ofNullable(null).orElseThrow(() -> new RuntimeException("unkown"));
}
// 运行结果:
unkown
unkown
java.lang.RuntimeException: unkown
三. Optional 使用场景
3.1. 空判断
空判断主要是用于不知道当前对象是否为 NULL 的时候,需要设置对象的属性。不使用 Optional 时候的代码如下:
if(null != order) {
order.setAmount(orderInfoVo.getAmount());
}
使用 Optional 时候的代码如下:
Optional.ofNullable(order).ifPresent(o -> o.setAmount(orderInfoVo.getAmount()));
使用 Optional 实现空判断的好处是,只有一个属性赋值的时候可以压缩代码为一行,这样做的话,代码会相对简洁。
3.2. 断言
在维护一些老旧的系统的时候,很多情况下外部的传参没有做空判断,因此需要写一些断言代码如:
if (null == orderVo.getAmount()){
throw new IllegalArgumentException(String.format("%s订单的amount不能为NULL",orderVo.getOrderId()));
}
if (StringUtils.isBlank(orderVo.getAddress()){
throw new IllegalArgumentException(String.format("%s订单的address不能为空",orderVo.getOrderId()));
}
使用 Optional 后的断言代码如下:
Optional.ofNullable(orderVo.getAmount()).orElseThrow(()-> new IllegalArgumentException(String.format("%s订单的amount不能为NULL", orderVo.getOrderId())));
Optional.ofNullable(orderVo.getAddress()).orElseThrow(()-> new IllegalArgumentException(String.format("%s订单的address不能为空", orderVo.getOrderId())));
3.3. Guava 中的 Optional
Optional 语法是在 JDK1.8 版本后才开始引入的,那还在用 JDK1.8 版本之前的老项目怎么办呢?这就需要用到 Guava 库中提供的 Optional 接口来帮助优雅地处理 null 对象问题,其本质也是在可能为 null 的对象上做了一层封装,使用起来和 JDK 本身提供的 Optional 接口没有太大区别。
你只需要在你的项目里引入 Google 的 Guava 库,即可享受到和 Java8 版本开始提供的 Optional一样的待遇。坐标如下:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
四. 总结
4.1. 使用误区
关于使用Optional的误区有以下:
1. 正确的使用创建方法,不确定是否为 null 时尽量选择 ofNullable 方法;
2. 通过源代码会发现,它并没有实现 java.io.Serializable 接口,因此应避免在类属性中使用,防止意想不到的问题;
3. 避免直接调用 Optional 对象的 get() 和 isPresent() 方法,尽量多使用 map()、filter()、orElse()等方法来发挥 Optional 的作用。
4.2. 本质特征
1. Optional 作为一个容器承载对象,提供方法适配部分函数式接口,结合部分函数式接口提供方法实现 NULL 判断、过滤操作、安全取值、映射操作等等。
2. Optional 一般使用场景是用于方法返回值的包装,当然也可以作为临时变量从而享受函数式接口的便捷功能。
3. Optional 只是一个简化操作的工具,可以解决多层嵌套代码的节点空判断问题(例如简化箭头型代码)。
本文详细介绍了Java8中的Optional类,包括其用法(创建、值获取、检查、安全访问、过滤和转换),使用场景(空判断和断言)以及常见误区。同时提到了Guava库中的Optional实现。
933

被折叠的 条评论
为什么被折叠?



