Java8 之 Optional 详解

目录

一. 前言

二. Optional 用法

2.1. 概要

2.2. 创建 Optional 对象

2.3. 获取 Optional 对象的值

2.4. 检查 Optional 对象是否为空

2.5. 安全访问 Optional 对象的值

2.6. 处理过滤操作

2.7. 处理转换操作

2.8. 使用默认值

三. Optional 使用场景

3.1. 空判断

3.2. 断言

3.3. Guava 中的 Optional

四. 总结

4.1. 使用误区

4.2. 本质特征


一. 前言

    根据 Oracle 文档,Optional 是一个容器对象,可以包含也可以不包含非 null 值。Optional 在Java 8中引入,目的是解决 NullPointerException 的问题。本质上,Optional 是一个包装器类,其中包含对其他对象的引用。在这种情况下,对象只是指向内存位置的指针,并且也可以指向任何内容。从其他角度看,Optional 提供一种类型级解决方案来表示可选值而不是空引用。

二. Optional 用法

2.1. 概要

    Optional 类的 Javadoc 描述如下:这是一个可以为 null 的容器对象。如果值存在则 isPresent()方法会返回 true,调用 get() 方法会返回该对象。如果值不存在则 isPresent() 方法会返回 false,调用 get() 方法会 NPE。

方法描述
empy()创建一个空的 Optional 对象
of(T)把指定的值封装为 Optional 对象,如果指定的值为 null,则抛出 NullPointerException
ofNullable(T)把指定的值封装为 Optional 对象,如果指定的值为 null,则创建一个空的 Optional 对象
get()如果创建的 Optional 中有值存在,则返回此值,否则抛出 NoSuchElementException
isPresent()如果创建的 Optional 中的值存在,返回 true,否则返回 false
ifPresent()如果创建的 Optional 中的值存在,则执行该方法的调用,否则什么也不做
filter()如果创建的 Optional 中的值满足 filter() 中的条件,则返回包含该值的 Optional 对象,否则返回一个空的 Optional 对象
map()如果创建的 Optional 中的值存在,对该值执行提供的 Function 函数调用
flatMap()如果创建的 Optional 中的值存在,就对该值执行提供的 Function 函数调用,返回一个 Optional 类型的值,否则就返回一个空的 Optional 对象
orElse(T)如果创建的 Optional 中有值存在,则返回此值,否则返回一个默认值
orElseGet()如果创建的 Optional 中有值存在,则返回此值,否则返回一个由 Supplier 接口生成的值
orElseThrow如果创建的 Optional 中有值存在,则返回此值,否则抛出一个由指定的 Supplier 接口生成的异常

2.2. 创建 Optional 对象

Optional.empty():返回一个空的 Optional 实例,Optional 的值不存在。如果对象为空,请避免与 Option.empty() 返回的实例的值比较 。因为不能保证它是一个单例,反之,应该使用isPresent()。

Optional.of(T value):创建一个 Optional 实例,该方法为静态方法,所以需要一个非 null 参数,也就是 value 必须非空;如果 value 为空,则抛出 NullPointerException。

Optional.ofNullable(T value):创建一个包含可能为 null 的值的 Optional 对象。

源码如下:

private static final Optional<?> EMPTY = new Optional<>();public static<T> Optional<T> empty() {@SuppressWarnings("unchecked")Optional<T> t = (Optional<T>) EMPTY;return t;
}public static <T> Optional<T> of(T value) {return new Optional<>(value);
}public static <T> Optional<T> ofNullable(T value) {return value == null ? empty() : of(value);
}

示例如下:

public void testConstructor() {// 1、创建一个包装对象值为空的Optional对象Optional<String> optStr = Optional.empty();// 2、创建包装对象值非空的Optional对象Optional<String> optStr1 = Optional.of("optional");// 3、创建包装对象值允许为空的Optional对象Optional<String> optStr2 = Optional.ofNullable(null);
}

2.3. 获取 Optional 对象的值

get():获取 Optional 对象中的值。如果 Optional 对象为空,则抛出 NoSuchElementException 异常。

源码如下:

public T get() {if (value == null) {throw new NoSuchElementException("No value present");}return value;
}

示例如下:

public void testGet() {Optional<String> optional = Optional.of("thinkwon");Optional<String> optional1 = Optional.ofNullable(null);System.out.println(optional.get());System.out.println(optional1.get());
}// 运行结果:
thinkwonjava.util.NoSuchElementException: No value present

2.4. 检查 Optional 对象是否为空

isPresent()判断 value 是否存在,不为 NULL 返回 true,如果为 NULL 则返回 false。

源码如下:

public boolean isPresent() {return value != null;
}

示例如下:

public void testIsPresent() {Optional<String> optional = Optional.of("thinkwon");Optional<String> optional1 = Optional.ofNullable(null);System.out.println(optional.isPresent());System.out.println(optional1.isPresent());
}// 运行结果:
true
false

注意:下面例子的用法不但没有减少 null 的防御性检查,而且增加了 Optional 包装的过程,违背了 Optional 设计的初衷,因此开发中要避免这种糟糕的使用:

public void testIsPresent() {Optional<String> optional = Optional.ofNullable(null);if (optional.isPresent()) {System.out.println(optional.get());}
}

试想一下如果先用 isPresent() 方法获得是否存在,然后决定是否调用 get() 方法,那和使用 if else 判断并无二致。

2.5. 安全访问 Optional 对象的值

ifPresent(Consumer<? super T> consumer):接受一个 Consumer 对象(消费函数),如果包装对象的值非空,运行 Consumer 对象的 accept() 方法。

源码如下:

public void ifPresent(Consumer<? super T> consumer) {if (value != null)consumer.accept(value);
}

示例如下:

public void testIfPresent() {Optional<String> optional = Optional.of("thinkwon");optional.ifPresent(s -> System.out.println("the String is " + s));
}// 运行结果:
the String is thinkwon

2.6. 处理过滤操作

filter(Predicate<? super T> predicate):对 Optional 对象中的值进行过滤操作,返回一个满足条件的 Optional 对象。

源码如下:

public Optional<T> filter(Predicate<? super T> predicate) {Objects.requireNonNull(predicate);if (!isPresent())return this;elsereturn predicate.test(value) ? this : empty();
}

示例如下:

public void testFilter() {Optional.of("thinkwon").filter(s -> s.length() > 2).ifPresent(s -> System.out.println("The length of String is greater than 2 and String is " + s));
}// 运行结果:
The length of String is greater than 2 and String is thinkwon

2.7. 处理转换操作

map(Function<? super T, ? extends U> mapper):将 Optional 中的包装对象用 Function 函数进行运算,并包装成新的 Optional 对象(包装对象的类型可能改变)。

flatMap(Function<? super T, Optional<U>> mapper):跟 map() 方法不同的是,入参 Function 函数的返回值类型为 Optional<U> 类型,而不是 U 类型,这样 flatMap() 能将一个二维的 Optional 对象映射成一个一维的对象。

源码如下:

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));}
}public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {Objects.requireNonNull(mapper);if (!isPresent()) {return empty();} else {@SuppressWarnings("unchecked")Optional<U> r = (Optional<U>) mapper.apply(value);return Objects.requireNonNull(r);}
}

示例如下:

public void testMap() {Optional<String> optional = Optional.of("thinkwon").map(s -> s.toUpperCase());System.out.println(optional.get());Optional<String> optional2 = Optional.of("thinkwon").flatMap(s -> Optional.ofNullable(s.toUpperCase()));System.out.println(optional2.get());
}// 运行结果:
THINKWON
THINKWON

2.8. 使用默认值

orElse():即如果包装对象值非空,返回包装对象值,否则返回入参 other 的值(默认值)。

orElseGet():与 orElse() 方法类似,区别在于 orElseGet() 方法的入参为一个 Supplier 对象,用Supplier 对象的 get() 方法的返回值作为默认值。

orElseThrow():与orElseGet()方法非常相似,入参都是 Supplier 对象,只不过 orElseThrow() 的Supplier 对象必须返回一个 Throwable 异常,并在 orElseThrow() 中将异常抛出,orElseThrow()方法适用于包装对象值为空时需要抛出特定异常的场景。

源码如下:

public T orElse(T other) {return value != null ? value : other;
}public T orElseGet(Supplier<? extends T> supplier) {return value != null ? value : supplier.get();
}public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {if (value != null) {return value;} else {throw exceptionSupplier.get();}
}

示例如下:

public void testOrElse() {String unkown = (String) Optional.ofNullable(null).orElse("unkown");System.out.println(unkown);String unkown2 = (String) Optional.ofNullable(null).orElseGet(() -> "unkown");System.out.println(unkown2);Optional.ofNullable(null).orElseThrow(() -> new RuntimeException("unkown"));
}// 运行结果:
unkown
unkown
java.lang.RuntimeException: unkown

三. Optional 使用场景

3.1. 空判断

空判断主要是用于不知道当前对象是否为 NULL 的时候,需要设置对象的属性。不使用 Optional 时候的代码如下:

if(null != order) {order.setAmount(orderInfoVo.getAmount());
}

使用 Optional 时候的代码如下:

Optional.ofNullable(order).ifPresent(o -> o.setAmount(orderInfoVo.getAmount()));

使用 Optional 实现空判断的好处是,只有一个属性赋值的时候可以压缩代码为一行,这样做的话,代码会相对简洁。

3.2. 断言

在维护一些老旧的系统的时候,很多情况下外部的传参没有做空判断,因此需要写一些断言代码如:

if (null == orderVo.getAmount()){throw new IllegalArgumentException(String.format("%s订单的amount不能为NULL",orderVo.getOrderId()));
}if (StringUtils.isBlank(orderVo.getAddress()){throw new IllegalArgumentException(String.format("%s订单的address不能为空",orderVo.getOrderId()));
}

使用 Optional 后的断言代码如下:

Optional.ofNullable(orderVo.getAmount()).orElseThrow(()-> new IllegalArgumentException(String.format("%s订单的amount不能为NULL", orderVo.getOrderId())));Optional.ofNullable(orderVo.getAddress()).orElseThrow(()-> new IllegalArgumentException(String.format("%s订单的address不能为空", orderVo.getOrderId())));

3.3. Guava 中的 Optional

    Optional 语法是在 JDK1.8 版本后才开始引入的,那还在用 JDK1.8 版本之前的老项目怎么办呢?这就需要用到 Guava 库中提供的 Optional 接口来帮助优雅地处理 null 对象问题,其本质也是在可能为 null 的对象上做了一层封装,使用起来和 JDK 本身提供的 Optional 接口没有太大区别。

你只需要在你的项目里引入 Google 的 Guava 库,即可享受到和 Java8 版本开始提供的 Optional一样的待遇。坐标如下:

<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId>
</dependency>

四. 总结

4.1. 使用误区

关于使用Optional的误区有以下:

1. 正确的使用创建方法,不确定是否为 null 时尽量选择 ofNullable 方法;

2. 通过源代码会发现,它并没有实现 java.io.Serializable 接口,因此应避免在类属性中使用,防止意想不到的问题;

3. 避免直接调用 Optional 对象的 get() 和 isPresent() 方法,尽量多使用 map()、filter()、orElse()等方法来发挥 Optional 的作用。

4.2. 本质特征

1. Optional 作为一个容器承载对象,提供方法适配部分函数式接口,结合部分函数式接口提供方法实现 NULL 判断、过滤操作、安全取值、映射操作等等。

2. Optional 一般使用场景是用于方法返回值的包装,当然也可以作为临时变量从而享受函数式接口的便捷功能。

3. Optional 只是一个简化操作的工具,可以解决多层嵌套代码的节点空判断问题(例如简化箭头型代码)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/187343.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RPC与HTTP的详细比较

RPC 示例&#xff08;使用 gRPC&#xff09; 在这个例子中&#xff0c;我们使用 gRPC&#xff08;一个流行的 RPC 框架&#xff09;来演示 RPC 的基本用法。我们创建一个简单的计算器服务&#xff0c;客户端可以调用服务器上的加法操作。 服务定义 - Calculator.proto&#x…

深度解析 Dockerfile:构建可重复、可扩展的Docker镜像

文章目录 什么是Dockerfile&#xff1f;Dockerfile的基本结构常用Dockerfile指令解析1. FROM2. LABEL3. WORKDIR4. COPY5. RUN6. EXPOSE7. ENV8. CMD 构建可重复、可扩展的Docker镜像1. 指定基础镜像的版本2. 合理使用缓存3. 精简镜像4. 使用多阶段构建 总结 &#x1f388;个人…

提升网页交互体验的秘密武器——防抖和节流

说在前面 在现代Web开发中&#xff0c;提高网页性能是至关重要的。本文介绍了防抖和节流这两种常用的性能优化技术&#xff0c;通过控制函数的执行频率&#xff0c;有效减少不必要的计算和网络请求&#xff0c;从而提升用户体验和页面加载速度。 函数节流 节流是指限制一个函数…

【分享】centos7vim异常

问题描述: 虚拟机安装centos7&#xff0c;安装vim后异常&#xff0c;上下左右键会变成abcd,退格键无法删除。 系统版本: CentOS Linux release 7.5.1804 (Core) 原因: 暂时未知&#xff0c;有可能是vim安装时&#xff0c;一个组件未安装导致。后续追溯 解决方案: echo &quo…

nginx之rewrite

rewrite rewrite 作用是地址重定向&#xff0c;语法&#xff1a;rewrite regex replacement[flag]; 根据 regex&#xff08;正则表达式&#xff09;匹配请求地址&#xff0c;然后跳转到 replacement&#xff0c;结尾是flag标记 如下例子&#xff0c;请求地址是 http://192.168.…

异常 Exception 练习题 (未完成)

异常 Exception 练习题 try-catch异常处理1234 异常1&#xff08;没有自己写&#xff09;234 try-catch异常处理 1 class Exception01 {public static int method() {try {String[] names new String[3];//String[]数组if (names[1].equals("tom")) {//NullPointe…

血的教训---入侵redis并免密登录redis所在服务器漏洞复现

血的教训—入侵redis并免密登录redis所在服务器漏洞复现 今天就跟着我一起来入侵redis并免密登录redis所在服务器吧&#xff0c;废话不多说&#xff0c;我们直接开始吧。 这是一个体系的学习步骤&#xff0c;当然如果基础扎实的话可以继续往下面看 以下都是关联的文章&#xff…

Unity3D 导出的apk进行混淆加固、保护与优化原理(防止反编译)

Unity3D 导出的apk进行混淆加固、保护与优化原理&#xff08;防止反编译&#xff09; 目录 前言&#xff1a; 准备资料&#xff1a; 正文&#xff1a; 1&#xff1a;打包一个带有签名的apk 2&#xff1a;对包进行反编译 3&#xff1a;使用ipaguard来对程序进行加固 前言&…

C++ 学习之匿名名字空间的使用细节

匿名命名空间&#xff08;anonymous namespace&#xff09;是C中的一种特殊命名空间&#xff0c;它没有显式的名称。匿名命名空间可以用来定义仅在当前文件中可见的全局变量、函数和类。 由于没有名字&#xff0c;所以相当于直接引入&#xff0c;但是没有引入定义 如果发生冲…

小程序如何刷新当前页面?

在小程序中&#xff0c;刷新当前页面通常有两种方法&#xff1a; 使用 wx.navigateBack 方法&#xff1a; wx.navigateBack({delta: 1 }) 这将返回上一页&#xff0c;并刷新页面。你可以通过调整 delta 参数来控制返回的页面数。例如&#xff0c;如果你想要返回到两页之前的页…

Echarts 柱状图添加标记 最大值 最小值 平均值

标记 最大值 最小值 series: [//图表配置项 如大小&#xff0c;图表类型{name: 图例,type: bar,//图表类型data: [{value: 500,time: 2012-11-12},{value: 454,time: 2020-5-17},{value: 544,time: 2022-1-22},{value: 877,time: 2013-1-30}, {value: 877,time: 2012-11-12}] …

AS 之 gradle 命令

文章目录 1、命令大全2、编译命令2.1 检查依赖并编译打包2.2 编译并打 Debug 包2.3 编译打出 Debug 包并安装2.4 编译并打出 Release 包2.5 编译打出 Release 包并安装2.6 Debug/Release 编译并打印日志 3、清除命令4、卸载命令4.1 卸载 Debug/Release 安装包4.2 adb 卸载 5、调…

Webpack 生产环境

文章目录 前言配置NPM Scripts指定 mode压缩(Minification)源码映射(Source Mapping)CLI 替代选项后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;webpack &#x1f431;‍&#x1f453;博主在前端领域还有很多知识和技术需要掌握&#…

Ubuntu18.04安装LIO-SAM保姆级教程

目录 1 LIO-SAM的安装前要求 1.1 ROS安装:参考我的另一篇博客 1.2 gtsam安装(参考LeGO-LOAM 1.2节) 2 安装

【linux网络】补充网关服务器搭建,综合应用SNAT、DNAT转换,dhcp分配、dns分离解析,nfs网络共享以及ssh免密登录

目录 linux网络的综合应用 1&#xff09;网关服务器&#xff1a;ens35&#xff1a;12.0.0.254/24&#xff0c;ens33&#xff1a;192.168.100.254/24&#xff1b;Server1&#xff1a;192.168.100.101/24&#xff1b;PC1和server2&#xff1a;自动获取IP&#xff1b;交换机无需…

MATLAB算法实战应用案例精讲-【图像处理】SLAM技术详解(基础篇)(二)

目录 知识储备 SLAM基础知识 算法原理 LINS算法 算法框架 理论与代码分析

Ubuntu 20.04 for NVIDIA V100 GPU安装手册

安装Ubuntu 20.04.3 LTS版本 image.png 安装Ubuntu 20.04按照安装提示&#xff0c;仔细选择每一项&#xff0c;基本默认即可。 系统中查看GPU信息 系统安装完成之后&#xff0c;进入系统&#xff0c;使用lspci 命令查询一下GPU是否存在、型号信息是什么。 bpangbobpang:\~$…

【Centos8】下载 MySQL8 并开启远程连接

本文将记录一下 centos8 下载 mysql8 的安装命令&#xff0c;防止下一次安装的时候还需要查询相关资料。&#x1f923; 下载 mysql # 查看是否有 mysql&#xff0c;如果有则需要卸载 yum list installed mysql |grep mysql # or rpm -qa |grep mysql# 查看是否有 mysql 残余文…

传统算法:使用 Pygame 实现插入排序

使用 Pygame 模块实现了插入排序的动画演示。首先,它生成一个包含随机整数的数组,并通过 Pygame 在屏幕上绘制这个数组的条形图。接着,通过插入排序算法对数组进行排序,动画效果可视化每一步的排序过程。在排序的过程中,程序将当前元素插入到已排序的部分,通过适度的延迟…

plt绘制表格

目录 1、绘制简单表格 2、将字体居中 3、为每个表格添加背景 4、添加透明度 5、不显示表格标题 6、将pandas的表格列转行显示 7、关闭表格边框 8、设置表格长宽、字体大小 9、利用色系指定表格颜色 10、修改字体颜色、边框粗细 1、绘制简单表格 import pandas as pd…