Optional
# Optional
本文介绍下 Optional 的概念、基本使用及其带来的好处。
本文主要参考王二哥的文章:Java 8 Optional 最佳指南 (opens new window),结合自己的经验撰写而成
# NPE
在传统开发中,我们经常需要判空(判断是否为 null),不然就经常产生空指针异常,例如:
public class OptionalDemo1NPE {
public static void main(String[] args) {
String s = null;
s.equals("hello");
}
}
2
3
4
5
6
运行结果如下:
$ javac OptionalDemo1NPE.java
$ java OptionalDemo1NPE
Exception in thread "main" java.lang.NullPointerException
at LearnOptionalDemo1NPE.main(LearnOptionalDemo1NPE.java:4)
2
3
4
在工作中,有很多需要判空的场景,例如:
if(null != user){
Address address = user.getAddress();
if(null != address){
Country country = address.getCountry();
if(null != country){
//.......
}
}
}
2
3
4
5
6
7
8
9
一旦代码量大起来,就会有大量的判空代码。
为了解决空指针异常,Google 公司著名的 Guava (opens new window) 项目引入了 Optional 类,Guava 通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到 Google Guava 的启发,Optional 类已经成为 Java 8 类库的一部分
Optional 实际上是个容器:它可以保存类型 T 的值,或者仅仅保存 null。Optional 提供很多有用的方法,这样我们就不用显式进行空值检测。
为此,Java8 引入了 Optional 类。
Optional 实际上是个容器:它可以保存类型 T 的值,或者仅仅保存 null。Optional 提供很多有用的方法,这样我们就不用显式进行空值检测。
# Optional 原理
在继续讲 Optional 的使用之前,先简单说下其原理,免得后续介绍的过程中一头雾水。
Optional,其实就是一个类,可以创建它的对象,可以调用它的一些方法。
Optional 可以帮我们保存一个对象(不管是不是 null),并且自带一个方法判断其是否为 null;如果不是,则返回保存的对象;如果是,则可以返回默认值(非 null),这样就简化了 NPE 的判断。
Optional 可以理解为是一个容器,容器里可能是空的(保存的对象为 null),也可以不是空的
# Optional 对象的创建
Optional 常见的三种创建方式:
Optional.empty(T)
:创建一个空的 Optional 对象,较少使用Optional.of(T)
:创建一个非空的 Optional 对象,较少使用Opeional.ofNullable(T)
:创建一个可以为空,也可以不为空的 Optional 对象,较多使用
以上方法都是静态方法。
1)使用静态方法 empty()
创建一个空的 Optional 对象:
Optional<String> empty = Optional.empty();
System.out.println(empty); // 输出:Optional.empty
2
2)使用静态方法 of()
创建一个非空的 Optional 对象:传递给 of()
方法的参数不能为 null,否则仍然会抛出 NullPointerException
Optional<String> opt = Optional.of("PeterJXL");
System.out.println(opt); // 输出:Optional[PeterJXL]
2
3)使用静态方法 ofNullable()
创建一个即可空又可非空的 Optional 对象
String name = null;
Optional<String> optOrNull = Optional.ofNullable(name);
System.out.println(optOrNull); // 输出:Optional.empty
2
3
ofNullable()
方法内部有一个三元表达式,如果为参数为 null,则返回私有常量 EMPTY;否则使用 new 关键字创建了一个新的 Optional 对象——不会再抛出 NPE 异常了。
# 判断是否为 null
创建了 Optional 对象后,就可以判断是否为 null 了,主要有 2 个方法:
isPresent()
:判断其存储的值是否为 null,是则返回 true,否则返回 false(取代了obj != null
的判断)。Present 的含义是 “存在的”isEmpty()
:Java11 新增的方法,是 null 则返回 true,否则返回 false(和isPresent()
输出相反)
Optional<String> opt = Optional.of("PeterJXL");
System.out.println(opt.isPresent()); //true
Optional<String> optOrNull = Optional.ofNullable(null);
System.out.println(optOrNull.isPresent()); //false
// Java 11新方法 isEmpty
Optional<String> opt2 = Optional.of("PeterJXL");
System.out.println(opt.isEmpty()); // false
Optional<String> optOrNull2 = Optional.ofNullable(null);
System.out.println(optOrNull.isEmpty()); // true
2
3
4
5
6
7
8
9
10
11
12
至此,我们就可以用 isPresent()
方法对 Optional 对象进行判空后再执行相应的代码:
Optional<String> optOrNull = Optional.ofNullable(null);
if (optOrNull.isPresent()) {
System.out.println(optOrNull.get().length());
}
2
3
4
注:get 方法是 Optional 对象值的方法,但是如果 Optional 对象的值为 null,会抛出 NoSuchElementException 异常,后续我们会讲其他方法。
使用该种方式,代码量并不见得减少了多少。我们接着往下看
# 非空则执行表达式
上面仅仅是取代了 obj != null
的判断,但我们经常需要判断不为 null 之后,再执行一些语句,因此我们可以用 ifPresent()
(注意是 if,而不是 is)。我们可以直接将 Lambda 表达式传递给该方法,代码更加简洁,更加直观:
Optional<String> opt = Optional.of("PeterJXL");
opt.ifPresent(str -> System.out.println(str.length()));
2
Java 9 后还可以通过方法 ifPresentOrElse(action, emptyAction)
执行两种结果,非空时执行 action,空时执行 emptyAction。
Optional<String> opt2 = Optional.ofNullable("PeterJXL");
opt2.ifPresentOrElse(str -> System.out.println(str.length()), () -> System.out.println("is Null"));
2
# 设置与获取默认值
有时候,我们在创建(获取) Optional 对象的时候,需要一个默认值,orElse(Supplier<T>)
和 orElseGet(Supplier<T>)
方法就派上用场了。
在构造 Optional 对象时传入的 value 值为 null 时,就会调用参数里的函数:
public class OptionalDemo5DefaultValue {
public static void main(String[] args) {
String str = null;
String strOpt = Optional.ofNullable(str).orElse(createString());
String strOpt2 = Optional.ofNullable(str).orElseGet(() -> createString());
}
public static String createString(){
System.out.println("invoke createString");
return "zhang san";
}
}
2
3
4
5
6
7
8
9
10
11
12
这两个函数的区别:当 user 值不为 null 时,orElse
函数依然会执行 createUser()
方法,而 orElseGet
函数并不会执行 createUser()
方法,大家可自行测试。
在我们处理的业务数据量大的时候,这两者的性能就有很大的差异。
orElse()
有一个重构的方法,可以直接传入一个对象,参数类型和变量的类型要一致
String nullStr = null;
String name = Optional.ofNullable(nullStr).orElse("PeterJXL");
System.out.println(name); //PeterJXL
2
3
用于返回包裹在 Optional 对象中的值,如果该值不为 null,则返回;否则返回默认值。
orElseGet()
也有另一种写法:传入方法的引用
String strOpt3 = Optional.ofNullable(str).orElseGet(OptionalDemo5DefaultValue::createString);
# filter
之前我们仅仅是使用 isPresent
和 ifPresent
方法判断是否为 null,有时候我们判断不为 null 了之后,还需要判断对象是否符合我们的场景:例如,当我们校验密码的时候,除了不能为 null,还对密码的长度、复杂度等进行判断,此时我们可以用 Optional 提供的 filter()
方法
String password = "12345";
Optional<String> opt = Optional.ofNullable(password);
System.out.println(opt.filter(pwd -> pwd.length() > 5).isPresent()); //false
2
3
filter()
方法的参数类型为 Predicate(Java 8 新增的一个函数式接口),也就是说可以将一个 Lambda 表达式传递给该方法作为条件,如果表达式的结果为 false,则返回一个 EMPTY 的 Optional 对象,否则返回过滤后的 Optional 对象。
假设密码的长度要求在 6 到 10 位之间,那么还可以再追加一个条件:
Predicate<String> len6 = pwd -> pwd.length() > 6;
Predicate<String> len10 = pwd -> pwd.length() < 10;
password = "1234567";
opt = Optional.ofNullable(password);
boolean result = opt.filter(len6.and(len10)).isPresent();
System.out.println(result); //true
2
3
4
5
6
这次程序输出的结果为 true,因为密码变成了 7 位,在 6 到 10 位之间。假如使用 if-else 来判断,代码会很冗长,要有 3 个判断(是否为 null,是否大于 6,是否小于 10)
# map
有时候我们不仅需要判空对象,还需要对象的成员也不为空,也就是我们需要再定义一个 Optional:
String name = "PeterJXL";
Optional<String> nameOptional = Optional.ofNullable(name);
Optional<Integer> integerOptional = Optional.ofNullable(name.length());
2
3
此时我们可以用 Optional 的 map 方法,它可以按照一定的规则将原有 Optional 对象转换为一个新的 Optional 对象,原有的 Optional 对象不会更改:
integerOptional = nameOptional.map(String::length);
在上面这个例子中,map()
方法的参数 String::length
,意味着要 将原有的字符串类型的 Optional 按照字符串长度重新生成一个新的 Optional 对象,类型为 Integer。
# filter 和 map 结合
举个粒子,我们需要判断密码的长度,并且密码不能是简单的密码(例如 password,是弱密码),我们可以结合 Filter 和 Map:
String name = "PeterJXL";
Optional<String> nameOptional = Optional.ofNullable(name);
Optional<Integer> integerOptional = Optional.ofNullable(name.length());
integerOptional = nameOptional.map(String::length);
String password = "Password";
Optional<String> opt = Optional.ofNullable(password);
Predicate<String> len6 = pwd -> pwd.length() > 6;
Predicate<String> len10 = pwd -> pwd.length() < 10;
Predicate<String> eq = pwd -> pwd.equals("123456");
boolean result = opt.map(String::toLowerCase).filter(len6.and(len10).and(eq)).isPresent();
System.out.println(result); //true
2
3
4
5
6
7
8
9
10
11
12
13
14
map()
用于将密码转化为小写,filter()
用于判断长度以及是否是“password”。
# 深入 Optional
# Optional 对象的创建
Optional
的构造函数是 private 权限的,不能由外部调用的。而 Optional.empty(),
Optional.of(T value)
, Optional.ofNullable(T value)
是 public 权限,供我们所调用。
Optional 的本质,就是内部储存了一个真实的值,在构造函数里会调用 Objects 的 requireNonNull
方法:
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
2
3
而 Objects 里会直接判断其值是否为空:如果为空,则直接抛出异常。
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
2
3
4
5
流程图:
我们来逐个讲解创建 Optional 对象的三个方法
首先是 Optional.of(T value)
,源码如下
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
2
3
也就是说 of(T value)
函数内部调用了构造函数。根据构造函数的源码我们可以得出两个结论:
- 通过
of(T value)
函数所构造出的 Optional 对象,当 Value 值为空时,依然会报 NullPointerException。 - 通过
of(T value)
函数所构造出的 Optional 对象,当 Value 值不为空时,能正常构造 Optional 对象。
然后是 Optional.empty()
,源码如下:
private static final Optional<?> EMPTY = new Optional<>();
private Optional() {
this.value = null;
}
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
2
3
4
5
6
7
8
9
10
11
也就是说,Optional 类内部还维护一个 value 为 null 的对象,并创建了一个名为 EMPTY 的 Optional 对象来存储这个对象。而 empty()
的作用就是返回 EMPTY 对象。
最后讲讲我们常用的 ofNullable(T value)
,源码如下:
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
2
3
读者应该都能看得懂什么意思了。相比较 of(T value)
的区别就是:当 value 值为 null 时
of(T value)
会报 NullPointerException 异常;ofNullable(T value)
不会 throw Exception,直接返回一个EMPTY
对象。
因此,我们一般都是用 ofNullable
方法,而不是 of
方法,of
方法的应用场景笔者还没见过,欢迎补充。
# get 方法
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
2
3
4
5
6
可以看到,如果为空,还是会抛出异常,还不如抛出 NPE 呢 🙄 因此一般很少用 get 方法
# orElseThrow
除了 orElse(Supplier<T>)
和 orElseGet(Supplier<T>)
,还有一个 orElseThrow
方法,当 value 值为 null 时, 直接抛一个异常出去,用法如下所示
User user = null;
Optional.ofNullable(user).orElseThrow(()->new Exception("用户不存在"));
2
如果有判断为 null 后需抛出异常的场景,可以用 orElseThrow
简化
# map 源码
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));
}
}
2
3
4
5
6
7
8
可以看到 map 其实内部也是创建了一个 Optional 对象来实现转换。
# 实践
# 例 1 简化判空
以前写法
if(user!=null){
dosomething(user);
}
2
3
JAVA8 写法
Optional.ofNullable(user).ifPresent(u->{
dosomething(u);
});
2
3
# 例 2 简化多个判空
在函数方法中,以前写法
public String getCity(User user) throws Exception{
if(user!=null){
if(user.getAddress()!=null){
Address address = user.getAddress();
if(address.getCity()!=null){
return address.getCity();
}
}
}
throw new Excpetion("取值错误");
}
2
3
4
5
6
7
8
9
10
11
JAVA8 写法
public String getCity(User user) throws Exception{
return Optional.ofNullable(user)
.map(u-> u.getAddress())
.map(a->a.getCity())
.orElseThrow(()->new Exception("取指错误"));
}
2
3
4
5
6
# 例 3 简化判空和默认值
以前写法
public User getUser(User user) throws Exception{
if(user != null){
String name = user.getName();
if("zhangsan".equals(name)){
return user;
}
}else{
user = new User();
user.setName("zhangsan");
return user;
}
}
2
3
4
5
6
7
8
9
10
11
12
java8 写法
public User getUser(User user) {
return Optional.ofNullable(user)
.filter(u->"zhangsan".equals(u.getName()))
.orElseGet(()-> {
User user1 = new User();
user1.setName("zhangsan");
return user1;
});
}
2
3
4
5
6
7
8
9
# 总结
使用了 Optional 后,我们就可以使用链式编程(如同 Stream 流一样),虽然代码优雅了。但是,逻辑性没那么明显,可读性有所降低,在后续编程的时候应酌情考虑是否要使用。
本文所有代码已上传至 GitHub 和 Gitee:
Gitee:src/chapter20Java8 (opens new window)
GitHub:LearnJavaSE/src/chapter20Java8 (opens new window)
# 参考
Java 8 Optional 最佳指南 | Java 程序员进阶之路 (opens new window)
java8 之 Optional 判空,简化判空操作-今日头条 (opens new window)
JAVA8 之妙用 Optional 解决判断 Null 为空的问题_zjhred 的博客-CSDN 博客_optional.ofnull (opens new window)
Java8 新特性之 Optional_雨 ~ 旋律的博客-CSDN 博客_java8 optional (opens new window)