使用 Java8
有一段时间了,对于其中的 Optional
类使用较为频繁,所以写一篇文章记录
我不会说是因为老记不住调用
Api
才写的
Optional
类主要解决的问题是 Java
常见的的空指针异常 NullPointerException
从创建 Optional
的 API
来看,可以创建内容不为空或内容为空的类
同时,Optional
也是用来实现 函数式编程
的一个很大的进步,虽然代码精炼了,但是从 代码可读性
上来说并不友好。所以,根据实际业务场景来合理使用
场景模拟
一个学生类,其中包含姓名、班级类
班级类中包含课程类、教室位置等信息
课程类包括授课老师、课程名称等信息
假设:一个学生对应一个班级,一个班级对应一门课程
根据学生类→获取班级类→获取课程类
如果你想获取学生报的课程,那么代码是这样的
String courseName = student.getSchoolClass().getCourse().getCourseName();
这样的话是很可能发生空指针异常,如果想控制这个异常,代码是这样的
if (student != null) {
if (student.getSchoolClass() != null) {
SchoolClass schoolClass = student.getSchoolClass();
if (schoolClass != null) {
Course course = schoolClass.getCourse();
if (course != null) {
String courseName = course.getCourseName();
}
}
}
}
这种代码逻辑上确实感觉很明了,但是总觉得有一丝丝不优雅。可以看下 Optional
类的一些使用,看能否进行简化并控制异常的抛出
创建实例
Optional
类有三个创建实例的方法,分别是 Optional.empty()
、Optional.of(value)
、Optional.ofNullable(value)
,看分别对应什么样的需求和场景
Optional.empty()
此方法是声明一个空的Optional
类
Optional<String> str = Optional.empty();
这种方式创建出来的类在被赋值前不能够被访问,如果进行访问的话将会抛出异常
java.util.NoSuchElementException: No value present
可以使用另外一种方式来防止空值传入
Optional.of(value)
依据非空值创建一个Optional,如果传入值为空,会直接抛出 NullPointerException
Optional<String> nameStr = Optional.of("百万");
Optional.ofNullable(value)
如果对象即可能是 空 也可能是非 空,那么应该使用 Optional.ofNullable(value)
方法
访问 Optional 对象的值
使用 Optional
创建出来的对象是不能够直接使用,而是需要使用 .get()
方法获取到其中的值
@Test
public void testOptionalGet() {
String name = "马马马马马百万";
Optional<String> opt = Optional.ofNullable(name);
System.out.println(opt.get());
}
检查 Optional 对象不为空
检查 Optional
对象不为空有两种方式,分别是 isPresent()
、ifPresent()
isPresent()
如果Optional
调用了 isPresent()
方法,那么会返回一个 boolean
值
@Test
public void testOptionalIsPresent() {
String name = "马马马马马百万";
Optional<String> opt = Optional.ofNullable(name);
if (opt.isPresent()) {
System.out.println(opt.get());
}
}
ifPresent()
此方法不仅可以检查 Optional
是否为空,还有一个 Consumer(消费者)
参数,如果不为空,执行传入的 lambda
表达式
@Test
public void testOptionalGet() {
String name = "马马马马马百万";
Optional<String> opt = Optional.ofNullable(name);
opt.ifPresent(data -> System.out.println(opt.get()));
}
返回默认值
如果 Optional
为空返回默认值提供了两个方法,分别是 orElse()
、orElseGet()
orElse()
因为 student
为空,所以 target
值为 student2
如果 student
不为空,那么返回值就会是 student
本身
@Test
public void testOptionalOrElse() {
Student student = null;
Student student2 = new Student("马马马马马百万", 26);
Student target = Optional.ofNullable(student).orElse(student2);
}
orElseGet()
orElseGet()
方法在有值时返回本身,值为空时,它会执行作为参数传入的 Supplier(供应者) 函数式接口,并将返回其执行结果
@Test
public void testOptionalOrElseGet() {
Student student = null;
Student target = Optional.ofNullable(student).orElseGet(() -> new Student("马马马马马百万", 26));
}
orElse() 和 orElseGet() 的不同之处
orElse()
和 orElseGet()
在值为空时处理结果是一致的;But
在值不为空时,又是一番风景
@Test
public void testOptionalOrElse() {
Student student = null;
log.info("student orElse");
Student result1 = Optional.ofNullable(student).orElse(createStudent());
log.info("student orElseGet");
Student result2 = Optional.ofNullable(student).orElseGet(() -> createStudent());
}
public Student createStudent() {
log.info("creating student");
return new Student("马马马马马百万", 26);
}
打印输出
student orElse
creating student
student orElseGet
通过打印日志看出,orElseGet()
在 student
不为空时没有调用 createStudent()
方法,反观 orElse()
仍然执行了创建方法
所以不推荐使用 orElse()
作为返回默认值方法,当然在 一般情况
下,这种微量消耗不会出现问题
异常抛出 orElseThrow()
Optional
定义 orElseThrow()
作为异常抛出的 API,它会在对象为空时抛出一个异常
抛出的异常不一定非要是 RuntimeException()
,也可以是其它异常或项目自定义异常等
@Test
public void testOptionalOrElseThrow() {
String str = null;
Optional.ofNullable(str).orElseThrow(() -> new RuntimeException());
}
相当于
@Test
public void testOptionalOrElseThrow() {
String str = null;
if (str == null) {
throw new RuntimeException();
}
}
转换值 map()
map()
在工作中是使用比较多的,先来个使用 API 看看工作流程
@Test
public void testOptionalMap() {
Student student = new Student("马马马马马百万");
String studentName = Optional.ofNullable(student).map(Student::getName).orElse("-");
}
map()
是可能无限级联的,像文章开始举出的例子就可以使用 map()
来解决
如果在 map()
方法的调用链中任意一环节出现空的情况,直接走 orElse("-")
@Test
public void testOptionalMap() {
String courseName = Optional.ofNullable(student)
.map(Student::getSchoolClass)
.map(SchoolClass::getCourse)
.map(Course::getCourseName)
.orElse("-");
}
转换值 flatMap()
map()
和 flatMap()
两个函数作用上没有什么区别,区别在于 map()
入参是 Function<? super T, ? extends U>
,flatMap()
入参是 Function<? super T, Optional<U>>
如果说Student获取SchoolClass的get方法是这样的
public Optional<SchoolClass> getSchoolClass() {
return Optional.ofNullable(schoolClass);
}
那么在获取时的代码必须要是这种形式
String courseName = Optional.ofNullable(student)
.flatMap(Student::getSchoolClass)
.map(SchoolClass::getCourse)
.map(Course::getCourseName).orElse("-");
过滤值 filter()
filter()
接受一个 Predicate
参数,返回测试结果为 true
的值。如果测试结果为 false
,会返回一个空的 Optional
比如有这么一个场景,我想查询学生姓名为 “张三”
的一名学生,如果查询不到返回 “-”
String studentName = Optional.ofNullable(student)
.map(Student::getName)
.filter(data -> Objects.equals(data, "张三"))
.orElse("-");
使用样例
场景️️ I
检查学生的个人介绍中是否包含 特殊字符
,如果包含则抛出异常
Optional.ofNullable(student)
.map(Student::getDetails)
.filter(Student::isDetailsValid)
.orElseThrow(() -> new RuntimeException());
}
场景️ II
如果学生学号 为空
执行某一操作
Optional.ofNullable(student)
.map(Student::getStuNum)
.ifPresent(data -> xxxx(data));
场景️ III
查看学生名字是否叫 百万
,不是默认返回
Student student = Optional.ofNullable(student)
.filter(data -> Objects.equals(data.getName(), "百万"))
.orElseGet(() -> new Student("百万"));