Java8中Optional类的使用说明
作者:永动的图灵机 发布时间:2023-07-25 13:31:32
简介
optional类是java8中引入的针对NPE问题的一种优美处理方式,源码作者也希望以此替代null。
历史
1965年,英国一位名为Tony Hoare的计算机科学家在设计ALGOL W语言时提出了null引用的想法。Hoare选择null引用这种方式,“只是因为这种方法实现起来非常容易”。很多年后,他开始为自己曾经做过这样的决定而后悔不迭,把它称为“我价值百万的重大失误”。我们已经看到它带来的后果——程序员对对象的字段进行检查,判断它的值是否为期望的格式,最终却发现我们查看的并不是一个对象,而是一个空指针,它会立即抛出一个让人厌烦的NullPointerException异常[1]。
null带来的种种问题
错误之源。
NullPointerException是目前Java程序开发中最典型的异常。
代码膨胀。
它让你的代码充斥着深度嵌套的null检查,代码的可读性糟糕透顶。
自身是毫无意义的。
null自身没有任何的语义,尤其是,它代表的是在静态类型语言中以一种错误的方式对缺失变量值的建模。
破坏了Java的哲学。
Java一直试图避免让程序员意识到指针的存在,唯一的例外是:null指针。
在Java的类型系统上开了个口子。
null并不属于任何类型,这意味着它可以被赋值给任意引用类型的变量。这会导致问题,原因是当这个变量被传递到系统中的另一个部分后,你将无法获知这个null变量最初的赋值到底是什么类型。
方案
汲取Haskell和Scala的灵感,Java 8中引入了一个新的类java.util.Optional
场景引入
首先我们引入一个常见的两个场景
/**
* >1 引入,常规判断一个学生的学校是不是公立学校,判断是否成年
*/
public static boolean checkIsPublicV1(Student student) {
if (student != null) {
School school = student.getSchool();
if (school != null) {
return school.isPublicFlag();
}
}
throw new RuntimeException("参数异常");
}
public static String getAdultV1(Student student) {
if (student != null) {
int age = student.getAge();
if (age > 18) {
return student.getName();
}
}
return "无";
}
上述方式是我们常见的判读流程,optional就是针对每次空判断导致的代码欣赏性问题进行了一套解决方案
方法说明
构造函数
Optional(T var1)
源码
private final T value;
private Optional() {
this.value = null;
}
private Optional(T var1) {
this.value = Objects.requireNonNull(var1);
}
从源码可知,optional的构造器私有,不能直接创建,只能通过类中的其他静态方法创建,optional构造器支持一个泛型入参,且改参数不能为空
创建Optional对象
声明一个空的Optional: Optional
依据一个非空值创建Optional: Optional
可接受null的Optional: Optional
源码
empty()源码
private static final Optional
从源码可知,optional类中维护了一个null值的对象,使用empty静态方法即可返回该空值对象
of(T var0)源码
public static
返回常见的有参构造对象,注意由于构造器要求入参不能为空,因此of方法的入参为空的话,依然会报NPE异常
ofNullable(T var0)源码
public static
这个方法是对of方法的补强,当ofNullable方法的入参不为空是正常返回构造对象,当入参为空时,返回一个空值的optional对象,而不会抛出异常。
ofNullable方法大部分场景优于of的使用。
null引用和Optional.empty()有什么本质的区别吗?从语义上,你可以把它们当作一回事儿,但是实际中它们之间的差别非常大:如果你尝试解引用一个null,一定会触发NullPointerException,不过使用Optional.empty()就完全没事儿,它是Op-tional类的一个有效对象,多种场景都能调用,非常有用。
举例
public static void testOptionalBuild() {
// 1. 无法直接new 'Optional()' has private access in 'java.util.Optional'
// Optional
使用map从Optional对象中提取和转换值
Optional map(Function
源码
public Optional map(Function
当optional包裹的值为空时直接放回空对象,否则执行入参中的Function.apply方法
使用flatMap链接Optional对象
Optional flatMap(Function<? super T, Optional> var1)
源码
public Optional flatMap(Function<? super T, Optional> var1) {
Objects.requireNonNull(var1);
return !this.isPresent() ? empty() : (Optional)Objects.requireNonNull(var1.apply(this.value));
}
与map几乎一致。注意的是,入参的Function.apply方法中,返回类型为optional类型
举例
public static void testOptionalMap() {
Student student1 = getDefaultStudent();
Student student2 = getBackStudent();
// map
String school1 = Optional.ofNullable(student1).map(i -> i.getName()).orElse("无名");
String school2 = Optional.ofNullable(student2).map(i -> i.getName()).orElse("无名");
System.out.println("school1: " + school1 + "| school2: " + school2);
// flapMap 链式
String school3 = Optional.ofNullable(getOptionalStudent()).flatMap(i -> getOptionalStudent()).flatMap(i->i.getSchool()).map(i->i.getSchoolName()).orElse("没上大学");
System.out.println("school3: " + school3);
}
默认行为及解引用Optional对象1
T orElse(T var1)
T orElseGet(Supplier
T orElseThrow(Supplier
注:这三个方法方法不是静态方法,因此需要通过实例对象调用,一般跟在方法ofNullable后用于处理空值或返回值
源码
orElse源码
public T orElse(T var1) {
return this.value != null ? this.value : var1;
}
当optional中的包裹值不为空时返回包裹的值,若为空则返回orElse中的入参值
orElseGet源码
public T orElseGet(Supplier
与上个方法类似,当optional中的包裹值不为空时返回包裹的值,若为空执行orElseGet中的Supplier方法
orElseThrow源码
public
类似的,当optional中的包裹值不为空时返回包裹的值,若为空抛出orElseThrow中的Supplier.get的异常方法
举例
public static void testOptionalOrElse() {
// orElse
Student stu = getDefaultStudent();
Student backStudent = getBackStudent();
Student realStu1 = Optional.ofNullable(stu).orElse(backStudent);
System.out.println(realStu1);
// orElseGet
Student realStu2 = Optional.ofNullable(stu).orElseGet(()-> getBackStudent());
System.out.println(realStu2);
// orElseGet
Student realStu3 = Optional.ofNullable(stu).orElseThrow(()-> new RuntimeException("学生不存在"));
System.out.println(realStu3);
}
默认行为及解引用Optional对象2
boolean isPresent()
void ifPresent(Consumer
源码
isPresent()源码
public boolean isPresent() {
return this.value != null;
}
用户判断optional包裹的值是否为空,返回布尔值
ifPresent(Consumer var1)源码
public void ifPresent(Consumer
用户处理optional包裹的值不为空时,继续处理入参中Consumer.accept的方法。类似于 if(var!=null) {do sth}
举例
public static void testOptionalIfPresent() {
// isPresent()
Student student1 = getDefaultStudent();
Student student2 = getBackStudent();
boolean b1 = Optional.ofNullable(student1).isPresent();
boolean b2 = Optional.ofNullable(student2).isPresent();
System.out.println("b1: " + b1 + "| b2: " + b2);
// isPresent(Consumer)
Optional.ofNullable(student2).ifPresent(i-> acceptStudent(i, LocalDate.now()));
}
使用filter剔除特定的值
Optional filter(Predicate
源码
public Optional
用于对optional对象的过滤,当optional包裹的值不为空时返回该值,否则执行filter入参的Predicate.test方法
举例
public static void testOptionalFilter() {
Student student1 = getDefaultStudent();
Student student2 = getBackStudent();
System.out.println(student1);
System.out.println(student2);
Student student3 = Optional.ofNullable(student1).filter(i -> i.getAge() > 18).orElse(getBackStudent());
Student student4 = Optional.ofNullable(student2).filter(i -> i.getAge() > 18).orElse(getBackStudent());
System.out.println(student3);
System.out.println(student4);
}
实战
关于optional类的说明大致已经讲完,再回到开始的时候,提到的场景引入,结合optional进行改造
/**
* 实战1
* 针对原来的checkIsPublicV1进行改造
*/
public static boolean checkIsPublicV2(Student student) {
return Optional.ofNullable(student).map(i -> i.getSchool()).map(i -> i.isPublicFlag()).orElseThrow(() -> new RuntimeException("参数异常"));
}
/**
* 实战1
* 针对原来的getAdultV1进行改造
*/
public static String getAdultV2(Student student) {
return Optional.ofNullable(student).filter(i->i.getAge()>18).map(i->i.getName()).orElseGet(()->getDefaultStudent().getName());
}
附:
补充代码
public static void main(String[] args) {
//逐个放开
// 引入
// System.out.println(checkIsPublicV1(stu2));
// System.out.println(getAdultV1(stu2));
// optional方法
// testOptionalBuild();
// testOptionalOrElse();
// testOptionalIfPresent();
// testOptionalMap();
// testOptionalFilter();
// 实战
// System.out.println(getAdultV2(stu3));
// System.out.println(checkIsPublicV2(stu3));
}
/**========模型数据=======**/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
static class Student {
private String name;
private int age;
private School school;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
static class School {
private String schoolName;
private boolean publicFlag;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
static class StudentOpt {
private String name;
private int age;
private Optional
[1] 参考自java8实战
详细源码,请参考:github.com/chetwhy/clo…
来源:https://juejin.cn/post/7025925484462473246


猜你喜欢
- 我们在SpringBoot和MyBatis整合的时候,需要在SpringBoot中通过注解方式配置事务回滚1 Pojo类package co
- 一、线程的生命周期线程状态转换图:1、新建状态用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态。处于新生状
- 前文传送门:Netty启动流程服务端channel初始化注册多路复用回到上一小节的代码:final ChannelFuture initAn
- C#支持的位逻辑运算符如表2.9所示。运算符号意义运算对象类型运算结果类型对象数实例~位逻辑非运算整型,字符型整型1~a&位逻辑与运
- 1.介绍关机闹钟为Android中默认支持的功能,实现起来则需要满足一定的条件:自动开机、开机后响铃。对于自动开机来说,自动关机可以在应用层
- 本文实例分析了c#对象初始化顺序。分享给大家供大家参考。具体如下:using System;using System.Collections
- 扫码枪扫码效果等同于键盘录入,会回调dispatchKeyEvent键盘按下事件。开发环境:有线扫码枪,支持二维码代码1. 接收数据 /**
- 日期、数字格式化显示,是web开发中的常见需求,spring mvc采用XXXFormatter来处理,先看一个最基本的单元测试:packa
- 项目结构src com servletdemo
- 本文实例为大家分享了javafx实现时钟效果的具体代码,供大家参考,具体内容如下核心为三个函数:第一个为 public void dials
- 说到事件机制,可能脑海中最先浮现的就是日常使用的各种 listener,listener去监听事件源,如果被监听的事件有变化就会通知list
- 目录引入依赖Java中使用GraphQL的API无参数简单查询带参数简单查询GraphQL可以通过Java的API来实现数据的查询,通过特定
- 一、java发展史1.java之父:詹姆斯·高家林2.关键时间点:1996年Java(1.0)发布,2004年Java(5.0)发扬光大,2
- 前言上篇博客分享了创建链表传入二级指针的细节,那么今天就分享几个c语言课程实践设计吧。这些程序设计搞懂了的话相当于链表的基础知识牢牢掌握了,
- 本文实例为大家分享了Java身份证号码校验工具类的具体代码,供大家参考,具体内容如下import java.text.ParseExcept
- 前言值类型和引用类型,是c#比较基础,也必须掌握的知识点,但是也不是那么轻易就能掌握,今天跟着老胡一起来看看吧。 典型类型首先我们
- 核心代码迁移相对顺利,大致流程如下:一、创建项目 1) cd cocos2d-x-3.0rc0;&nbs
- 一、前端搭建1、前端用到js:uploadify(下载地址:http://www.uploadify.com/download/)、laye
- 简介:本篇博客主要包括recyclerview添加多种布局以及添加头布局和尾布局,还有item点击事件思路:主要重写Recyclerview
- BeanPostProcessor 的接口定义,可以实现提供自己的实例化逻辑,依赖解析逻辑等,也可以以后在Spring容器实例化完毕,配置和