简述

Optional 针对空指处理而设计的类型。
Java 8借鉴了ScalaHaskell,提供了一个新的Optional模板,可以用它来封装可能为空的引用。这是一个可以为null的容器对象。
使用 Optional 的好处是可以以一种专门针对null的处理方式,来避免值可能存在 null 导致出现的程序异常。你可以理解为:处理null,就是你的业务。
在实际使用过程中,你会发现 Optional 的灵活性有时候会让你想用在任何可能出现null的地方,不过凡事都有套路可循,只要清楚利弊就知道该如何选择。

从两个方面说一下 Optional

  • 常用API
  • 项目使用套路

只讲 API 不讲使用套路的文章都是耍流氓。

API说明

先看一下常用API,后面再讲实际使用场景。

  1. 构建API: 构建Optional对象:of()、ofNullable()、empty()
  2. 获取API: 获取Optional对象包装的值:get()、orElse()、orElseGet()、orElseThrow()
  3. 判断API:对Optional对象里包装的值做一些逻辑判断:isPresent()、ifPresent()、filter()
  4. 转换API:将Optional对象里包装的值转换成一个新的值:map()、flatMap();

构建API

Optional.of()

作用:构建 Optional 对象,不允许传入的值为null,传入就 null 马上抛异常。

这个API要慎用,一般在使用 Optional 时,就是要防住 null,这个API 上来就直接抛异常,一点机会也不给。

1
2
3
4
5
6
public static void testOf() {
Optional<String> op1 = Optional.of("Hello World");
System.out.println(op1.isPresent()); // 输出 true
System.out.println(op1.get()); // 输出 Hello
Optional<String> op2 = Optional.of(null); // 抛出异常
}

Optional.ofNullable()

常用API。

允许传入的值为 null,如果值为 null,返回一个空的 Optional 传入 null 并不抛异常。

使用 Optional.get() 获取值时,有值正常返回,值为 null 抛异常。

1
2
3
4
5
6
7
public static void testOfNullable() {
//传入不报错
Optional<String> name = Optional.ofNullable(null);
System.out.println(name); //直接输出是 Optional.empty
System.out.println(name.isPresent()); //判断是否有值
System.out.println(name.get()); // get() 抛异常
}

Optional.empty()

作用:创建一个空的 Optional 对象,一般很少直接这样写,都是通过 ofNullable 直接接住变量。

empty()方法创建的对象没有值,如果对 emptyOpt 变量调用isPresent()方法会返回false,
调用get()方法抛出NullPointerException异常。

1
2
3
4
5
public static void testEmpty() {
Optional<String> emptyOpt = Optional.empty();
System.out.println(emptyOpt.isPresent()); // 输出 false
System.out.println(emptyOpt.get()); // 抛异常
}

获取API

这一组API很常用:get、orElse、orElseGet、orElseThrow。
get() 使用简单,后面三个简单一些业务逻辑。

Optional.get()

作用:获取 Optional 中的数据。

可以看上一个例子。使用 Optional 时,如查值是 null,get 会抛异常。

orElse()

作用:如果有值就返回不执行,否则如果值为null,也会执行orElse();

这种做用是相当于在特定场景下的用法可以用它来代替if..else..来完成很简洁的逻辑判断。
看到 orElse 中只有一个String不能做别的事?当然不是,可以写一个方法,orElse调用该方法,就可以写其他代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.Optional;

public class OptionalTest {
public static void main(String[] args){
testOrElse(null);
}

public static void testOrElse(String nullValue) {
// 入参为 null,执行 orElse
String optional = Optional.ofNullable(nullValue).orElse("Su");
System.out.println(optional);
// 入参为 Susan,不执行 orElse
String nonNullOptional = Optional.ofNullable("Susan").orElse("Su");
System.out.println(nonNullOptional);
}
}

结果

Su
Susan

orElseGet()

作用:入参为null时,才会执行 orElseGet()。

和orElse的区别:
在optional为空值的情况下orElse和orElseGet都会执行,当optional不为空时,orElseGet不会执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.Optional;

public class OptionalTest {
public static void main(String[] args){
testOrElseGet(null);
}

public static void testOrElseGet(String nullValue) {
String optionalGet = Optional.ofNullable(nullValue).orElseGet(() -> "Xiao");
System.out.println(optionalGet);

String nonNullOptionalGet = Optional.ofNullable("Molly").orElseGet(() -> "Xiao");
System.out.println(nonNullOptionalGet);
}
}

结果

Xiao
Molly

orElseThrow()

作有:当参数为空时,抛异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.Optional;

public class OptionalTest {
public static void main(String[] args){
testOrElseThrow(null);
}

public static void testOrElseThrow(String nullValue) {
try {
Optional.ofNullable(nullValue)
.orElseThrow(()-> new Exception("参数为空"));
} catch (Exception e) {
e.printStackTrace();
}
}
}

结果

java.lang.Exception: 参数为空
at com.test.OptionalTest.lambda$testOrElseThrow$2(OptionalTest.java:30)
at java.util.Optional.orElseThrow(Optional.java:290)
at com.test.OptionalTest.testOrElseThrow(OptionalTest.java:30)
at com.test.OptionalTest.main(OptionalTest.java:9)

判断API

Optional.isPresent()

作用:判断变量是否为null

这个比较常用,用来提前判断一下值是否为空。实际业务场景中,很参数传入的时候,程序是不知道是否为null的,可以使用这个先进行判断。

1
2
3
4
5
6
7
8
9
10
public static void testIsPersent() {
//传入不报错
Optional<String> name = Optional.ofNullable(null);
System.out.println(name.isPresent()); //判断是否有值: true; false
if (name.isPresent()) {
System.out.println("有值");
} else {
System.out.println("空值");
}
}

Optional.ifPresent()

很常有的API。

作用:如果值不为null,则可以执行后续操作。

看这段代码,如果值为null不会进入 ifPresent,如果不为null,则进入。这样就不需要if来判断这种特性很方便写一个流畅的工作流风格代码。

1
2
3
4
5
6
7
public static void testIfPersent() {
String value = "test";
Optional.ofNullable(value)
.ifPresent((s) -> {
System.out.println(s);
}
}

Optional.filter()

作用:条件过滤,根据条件过滤不满足条件的数据。

1
2
3
4
5
6
7
8
9
10
public List<UserInfo> getUsers() {
List<UserInfo> userInfo = userService.getUsers();
List<UserInfo> newUserInfo = Optional
.ofNullable(userInfo)
.filter(u -> u.getPrice() < 1000)
.orElse(UserInfo.builder()
.build());
//直接 return,orElse处理了null,userInfo不会为null
return userInfo;
}

Optional.map()

作用:映射出新对象。map 时return 什么类型的数据,接收时就必须使用对应的泛型接住。

1
2
3
4
5
6
7
8
9
10
public static void testMap() {
String val1 = "test";
String val2 = null;
Optional<String> optional = Optional.ofNullable(val2);
Optional<String> newVal = optional.map((a) -> {
System.out.println(a);
return "bb";
});
System.out.println(newVal.get());
}

结果:
入参为 val1 时

bb

入参为 val2 时

Exception in thread "main" java.util.NoSuchElementException: No value present

套路

使用 Optional 时,Optional.get() 如果值为 null,还是会抛异常,那使用 Optional 有什么意义。
Optional 能不能当作返回出参,返回给外部调用。

先说第一个问题,意义在于,Optional 本身不会为 null,不会在被调用时出现空指针而导致异常。由于是通过 Optional 包裹可能出现空值的对象,所以多了一层保护机制。

Optional 不建议做为返回值,至于为什么后面说。

套路1 不返回null

保证返回的数据中绝对不返回null,保证不会因为null引起不可预见的异常。
结合 orElse,来保证如果下面的 list 中查出的数据是null,就返回一个空的ArrayList。
这种写法简单实用。

1
2
3
4
5
6
7
public List<User> getUsers() {
List<User> userInfo = userService.getUsers();
return Optional
.ofNullable(userInfo)
.orElse(new ArrayList());

}

套路2 先判断,后使用

业务中从一个Service中获得一个数据,那么先处理一下。

判断List是否为空

1
2
3
4
5
6
7
8
public static void testService() {
List<User> users = userService.getUsers();
Optional<String> usersOptional = Optional.ofNullable(users);
// 这里用 Optional 接住,再进行判断
if (usersOptional.isPresent()) {
//do somthine
}
}

判断List是否为空,不为空执行后续

1
2
3
4
5
6
7
public static void testService() {
List<User> users = userService.getUsers();
Optional<String> usersOptional = Optional.ofNullable(users);
usersOptional.ifPresent(user -> {
System.out.println(user.getSize());
})
}

这两种写法,其实很相近,该怎么选择呢,简化一下代码,如果只有在有值的情况下才处理,使用 ifPresent 的处理,是最简洁的。

1
2
3
4
5
6
7
8
9
10
public void test() {
//正解
Optional<User> userOpt = Optional.ofNullable(user);
userOpt.ifPresent(System.out.println(user.get()));

//而非
if (userOpt.isPresent()) {
System.out.println(user.get());
}
}

这两种方式都是用在处理没程不需要返回值的情况下。

套路3 结合 Stream 使用

开发中使用Stream应该是用的最多的,还是一样的套路,防止出现 List 为空。

1
2
3
4
5
6
7
8
9
10
11
12
List<Person> personList = personService.getPersons();
Optional.ofNullable(personList)
.orElseGet(() -> {
System.out.println("personList为null!");
return new ArrayList<>();
})
.stream()
.filter(Objects::nonNull)
.forEach(person -> {
System.out.println(person.getName());
System.out.println(person.getAge());
});

总结

Optional 的功能主要是在保证参数不出现null,通过提供的API来实现,让代码更加健壮。
健壮的代码有助于提高系统的稳定性,是一种不可多得的处理手段。即使不使用 Optional,也需要保证,不直接将 null 返回给上一级调用在方法内处理掉null。
不能相信调用的方法是安全的,需要自己对null有安全的处理。
Optional 只是简化了null的操作,即使没有 Optional 也要对null的处理放在一个重点关注的位置。