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