

一文掌握 Java8 的 Optional 的 6 种操作
source link: https://www.techug.com/post/master-6-operations-of-java-8-s-optional.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.


你好,我是看山。
Java8 中引入了一个特别有意思类:Optional
,一个可以让我们更加轻松的避免 NPE(空指针异常,NullPointException)的工具。
很久很久以前,为了避免 NPE,我们会写很多类似if (obj != null) {}
的代码,有时候忘记写,就可能出现 NPE,造成线上故障。在 Java 技术栈中,如果谁的代码出现了 NPE,有极大的可能会被笑话,这个异常被很多人认为是低级错误。Optional
的出现,可以让大家更加轻松的避免因为低级错误被嘲讽的概率。
定义示例数据
先定义待操作对象,万能的Student
类和Clazz
类(用到了 lombok 和 guava):
@Data @AllArgsConstructor @NoArgsConstructor public class Clazz { private String id; private String name; }
@Data @AllArgsConstructor @NoArgsConstructor public class Student { private String id; private String name; private Clazz clazz; }
然后定义一组测试数据:
final Clazz clazz1 = new Clazz("1", "高一一班"); final Student s1 = new Student("1", "张三", clazz1); final Student s2 = new Student("2", "李四", null); final List<Student> students = Lists.newArrayList(s1, s2); final List<Student> emptyStudents = Lists.newArrayList(); final List<Student> nullStudents = null;
创建实例:of、ofNullable
为了控制生成实例的方式,也是为了收紧空值Optional
的定义,Optional
将构造函数定义为private
。想要创建Optional
实例,可以借助of
和ofNullable
两个方法实现。
这两个方法的区别在于:of
方法传入的参数不能是null
的,否则会抛出NullPointerException
。所以,对于可能是null
的结果,一定使用ofNullable
。
代码如下:
Optional.of(students); Optional.of(emptyStudents); Optional.ofNullable(nullStudents);
Optional
类中还有一个静态方法:empty
,这个方法直接返回了内部定义的一个常量Optional<?> EMPTY = new Optional<>()
,这个常量的value
是null
。ofNullable
方法也是借助了empty
实现null
的包装:
public static <T> Optional<T> ofNullable(T value) { return value == null ? empty() : of(value); }
所以说,对于null
的Optional
包装类,指向的都是相同的实例对象,Optional.empty() == Optional.ofNullable(null)
返回的是true
。换句话说,空Optional
是单例的。
为了方便描述,下文中对值为
null
的Optional
统称为“空Optional
”。
获取数据:get
Optional
的get
方法有些坑人,先看下它的源码:
public T get() { if (value == null) { throw new NoSuchElementException("No value present"); } return value; }
也就是说,Optional
值为空时,使用get
方法将抛出NoSuchElementException
异常。如果不想抛出异常,或者能够 100%确定不是空Optional
,或者使用isPresent
方法判断。
如果能 100%确定不是空Optional
,那就没有必要使用Optional
包装,直接返回即可。如果需要使用isPresent
方法,那就和直接判空没有区别了。所以,无论是第一种情况还是第二种情况,都违背了设计这个类的初衷。
值为空判断:isPresent、ifPresent
isPresent
用来判断值是否为空,类似于obj != null
,ifPresent
可以传入一个Consumer
操作,当值不为空的时候,会执行Consumer
函数。比如:
final Optional<List<Student>> nullValue = Optional.ofNullable(nullStudents); if (nullValue.isPresent()) { System.out.println("value: " + nullValue.get()); }
上面的方法等价于:
nullValue.ifPresent(value -> System.out.println("value: " + value));
isPresent
判断的写法上是不是感觉很熟悉,感觉可以直接写为:
if (nullStudents != null) { System.out.println("value: " + nullStudents); }
对于isPresent
,如果是在自己可控的代码范围内,完全没有必要将值封装之后再判空。对于自己不可控的代码,后续的filter
或者map
方法可能比isPresent
更好用一些。
对于ifPresent
,在使用的时候会有一些限制,就是必须是非空Optional
的时候,在会执行传入的Consumer
函数。
值处理:map、flatMap
map
和flatMap
是对Optional
的值进行操作的方法,区别在于,map
会将结果包装到Optional
中返回,flatMap
不会。但是两个方法返回值都是Optional
类型,这也就要求,flatMap
的方法函数返回值需要是Optional
类型。
我们来看看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)); } }
可以看到,如果Optional
的值为空,map
直接返回Optional.EMPTY
,否则会执行函数结果,并使用Optional.ofNullable
包装并返回。也即是说,只要类结构允许,我们可以一直map
下去,就像是扒洋葱,一层一层,直到核心。
比如,我们要获取s2
所在班级名称,在定义的时候,我们将s2
的clazz
属性定义为 null,如果以前需要写为:
String clazzNameOld; if (s2 != null && s2.getClazz() != null && s2.getClazz().getName() != null) { clazzNameOld = s2.getClazz().getName(); } else { clazzNameOld = "DEFAULT_NAME"; }
现在借助Optional
可以写为:
final String clazzName = Optional.ofNullable(s2) .map(Student::getClazz) .map(Clazz::getName) .orElse("DEFAULT_NAME");
从代码上似乎没有多大改变,但是如果Clazz
内部还有类对象。或者,我们在if
判断的时候,少写一层检查呢?而且,map
的精巧还在于它的返回值永远是Optional
,这样,我们可以重复调用map
方法,而不需要中间被打断,增加各种判空逻辑。
值为空的处理:orElse、orElseGet、orElseThrow
这几个方法可以与map
操作结合,一起完成对象操作。当值为空时,orElse
和orElseGet
返回默认值,orElseThrow
抛出指定的异常。
orElse
和orElseGet
的区别是,orElse
方法传入的参数是明确的默认值,orElseGet
方法传入的参数是获取默认值的函数。如果默认值的构造过程比较复杂,需要经过一系列的运算逻辑,那一定要使用orElseGet
,因为orElseGet
是在值为空的时候,才会执行函数,并返回默认值,如果值不为空,则不会执行函数,相比于orElse
而言,减少了一次构造默认值的过程。
同样以上面的例子:
orElse
的写法:
final String clazzName = Optional.ofNullable(s2) .map(Student::getClazz) .map(Clazz::getName) .orElse(null);
orElseGet
的写法:
final String clazzName = Optional.of(s2) .map(Student::getClazz) .map(Clazz::getName) .orElseGet(() -> null);
如果clazz
属性一定不为空,为空则返回异常,可以使用orElseThrow
:
final String clazzName = Optional.of(s2) .map(Student::getClazz) .map(Clazz::getName) .orElseThrow(() -> new IllegalArgumentException("clazz属性不合法"));
条件过滤:filter
filter
方法提供的是值验证,如果值验证为 true,返回当前值;否则,返回空Optional
。比如,我们要遍历students
,找到班级属性为空的,打印学生 id:
for (final Student s : students) { Optional.of(s) .filter(x -> x.getClazz() == null) .ifPresent(x -> System.out.println(x.getId())); }
其他:equals、hashCode、toString
Optional
重写了这三个方法。因为Optional
可以认为是包装类,所以还是围绕这被包装的值重写这三个方法。下面给出这三个方法的源码:
public boolean equals(Object obj) { // 同一对象判断 if (this == obj) { return true; } // 类型判断 if (!(obj instanceof Optional)) { return false; } Optional<?> other = (Optional<?>) obj; // 最终还是值的判断 return Objects.equals(value, other.value); } public int hashCode() { // 直接返回值的hashCode return Objects.hashCode(value); } public String toString() { return value != null ? String.format("Optional[%s]", value) // 用到了值的toString结果 : "Optional.empty"; }
equals
方法,Optional.of(s1).equals(Optional.of(s2))
完全等价于s1.equals(s2)
。
hashCode
方法,直接返回的是值的 hashCode,如果是空Optional
,返回的是 0。
toString
方法,为了能够识别是Optional
,将打印数据包装了一下。如果是空Optional
,返回的是字符串“Optional.empty”;如果是非空,返回是是“Optional[值的 toString]”。
NPE 之所以讨厌,就是只要出现 NPE,我们就能够解决。但是一旦出现,都已经是事后,可能已经出现线上故障。偏偏在 Java 语言中,NPE 又很容易出现。Optional
提供了模板方法,有效且高效的避免 NPE。
接下来,我们针对上面的使用,总结一下:
Optional
是一个包装类,且不可变,不可序列化- 没有公共构造函数,创建需要使用
of
、ofNullable
方法 - 空
Optional
是单例,都是引用Optional.EMPTY
- 想要获取
Optional
的值,可以使用get
、orElse
、orElseGet
、orElseThrow
另外,还有一些实践上的建议:
- 使用
get
方法前,必须使用isPresent
检查。但是使用isPresent
前,先思考下是否可以使用orElse
、orElseGet
等方法代替实现。 orElse
和orElseGet
,优先选择orElseGet
,这个是惰性计算Optional
不要作为参数或者类属性,可以作为返回值- 尽量将
map
、filter
的函数参数抽出去作为单独方法,这样能够保持链式调
Recommend
-
85
一. Stream的特性 Stream是Java 8新增的接口,Stream可以认为是一个高级版本的 Iterator。它代表着数据流,流中的数据元素的数量可以是有限的,也可以是无限的。 Stream跟Iterator的差别是 无存储:Stream是基于数据源的对象,它本身不...
-
67
Java8 和 Java 9中并发工具的改变
-
59
Java8新特性及使用(一)
-
46
Java8 学习笔记,PPT 备忘录~ Java 发展史 JDK 5 JDK 6
-
45
在Java 8中API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。在Java中只要你应用的版本是java 8以上的话都能使用该API,但是在Android中如果API低于24时,是无法使用该java API,今天我们来介绍在Android...
-
65
查看 :point_right: LinkedList源码翻译 ...
-
102
查看 :point_right: ArrayList源码翻译 问...
-
76
本教程翻译整理自 github.com/winterbe/ja… 目录: 一、接口内允许添加默认实现的方法 二、Lambda 表达式 三、函数式接口 Functional Interface 四、便捷的引用类的构造器及方法 五、Lambda 访问外部变量及
-
15
引用处: JAVA8如何妙用Optional解决NPE问题详解 引言 NPE(NullPointerException)是调试程序最常见的异常。google一下有很多关于方法...
-
4
1. Optional Opitonal是java8引入的一个新类,目的是为了解决空指针异常问题。本质上,这是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为空。 Optional 是 Java 实现函数式编程的强劲一步,...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK