Optional是个好东西,如果用错了就太可惜了
liuian 2025-07-14 18:27 49 浏览
原文出处:
https://xie.infoq.cn/article/e3d1f0f4f095397c44812a5be
我们都知道,在Java 8新增了一个类 - Optional , 主要是用来解决程序中常见的 NullPointerException异常问题。但是在实际开发过程中很多人都是在一知半解的使用 Optional,类似 if (userOpt.isPresent()){...}这样的代码随处可见。如果是这样我更愿意看到老老实实的 null 判断,这样强行使用 Optional反而增加了代码的复杂度。
今天我给大家分享的这篇文章,便是 Java Optional 的一些 Best Practise 和一些反面的 Bad Practice,以供大家参考。
来自作者的说明
首先我们来看一下 Optional的作者 Brian Goetz 对这个 API 的说明:
“
Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent "no result", and using null for such was overwhelmingly likely to cause errors.
”
大意为,为了避免 null带来的错误,我们提供了一个可以明确表示空值的有限的机制。
基础理解
首先,Optional是一个容器,用于放置可能为空的值,它可以合理而优雅的处理 null。众所周知,null在编程历史上极具话题性,号称是计算机历史上最严重的错误,感兴趣可以读一下这篇文章:THE WORST MISTAKE OF COMPUTER SCIENCE,这里暂且不做过多讨论。在 Java 1.8 之前的版本,没有可以用于表示 null官方 API,如果你足够的谨慎,你可能需要常常在代码中做如下的判断:
if (null != user) {
//doing something
}
if (StringUtil.isEmpty(string)) {
//doing something
}
确实,返回值是 null的情况太多了,一不小心,就会产生 NPE,接踵而来的就是应用运行终止,产品抱怨,用户投诉。
1.8 之后,jdk 新增了 Optional来表示空结果。其实本质上什么也没变,只是增加了一个表达方式。Optional表示空的静态方法为 Optional.empty(),跟 null有什么本质区别吗?其实没有。翻看它的实现,Optional中的 value 就是 null,只不过包了一层 Optional,所以说它其实是个容器。用之后的代码可能长这样:
// 1
Optional<User> optionalUser = RemoteService.getUser();
if (!optionalUser.isPresent()) {
//doing something
}
User user = optionalUser.get();
// 2
User user = optionalUser.get().orElse(new User());
看起来,好像比之前好了一些,至少看起来没那么笨。但如果采用写法 1,好像更啰嗦了。
如果你对 kotlin 稍有了解,kotlin 的非空类型是他们大肆宣传的"卖点"之一,通过 var param!!在使用它的地方做强制的空检查,否则无法通过编译,最大程度上减少了 NPE。其实在我看来,Optional的方式更加优雅和灵活。同时,Optional也可能会带来一些误解。
下面先说一些在我看来不合适的使用方式:
Bad Practice
1. 直接使用 isPresent() 进行 if 检查
这个直接参考上面的例子,用 if判断和 1.8 之前的写法并没有什么区别,反而返回值包了一层 Optional,增加了代码的复杂性,没有带来任何实质的收益。其实 isPresent()一般用于流处理的结尾,用于判断是否符合条件。
list.stream()
.filer(x -> Objects.equals(x,param))
.findFirst()
.isPresent()
2. 在方法参数中使用 Optional
我们用一个东西之前得想明白,这东西是为解决什么问题而诞生的。Optional直白一点说就是为了表达可空性,如果方法参数可以为空,为何不重载呢?包括使用构造函数也一样。重载的业务表达更加清晰直观。
//don't write method like this
public void getUser(long uid,Optional<Type> userType);
//use Overload
public void getUser(long uid) {
getUser(uid,null);
}
public void getUser(long uid,UserType userType) {
//doing something
}
3. 直接使用 Optional.get
Optional不会帮你做任何的空判断或者异常处理,如果直接在代码中使用 Optional.get()和不做任何空判断一样,十分危险。这种可能会出现在那种所谓的着急上线,着急交付,对 Optional也不是很熟悉,直接就用了。这里多说一句,可能有人会反问了:甲方/业务着急,需求又多,哪有时间给他去做优化啊?因为我在现实工作中遇到过,但这两者并不矛盾,因为代码行数上差别并不大,只要自己平时保持学习,都是信手拈来的东西。
4. 使用在 POJO 中
估计很少有人这么用:
public class User {
private int age;
private String name;
private Optional<String> address;
}
这样的写法将会给序列化带来麻烦,Optional本身并没有实现序列化,现有的 JSON 序列化框架也没有对此提供支持的。
5. 使用在注入的属性中
这种写法估计用的人会更少,但不排除有脑洞的。
public class CommonService {
private Optional<UserService> userService;
public User getUser(String name) {
return userService.ifPresent(u -> u.findByName(name));
}
}
首先依赖注入大多在 spring 的框架之下,直接使用 @Autowired很方便。但如果使用以上的写法,如果 userService set 失败了,程序就应该终止并报异常,并不是无声无息,让其看起来什么问题都没有。
Best and Pragmatic Practice
API
在说最佳实践前,让我们来看一下 Optional都提供了哪些常用 API。
1. empty()
返回一个 Optional容器对象,而不是 null。建议常用
2. of(T value)
创建一个 Optional对象,如果 value 是 null,则抛出 NPE。不建议用
3. ofNullable(T value)
同上,创建一个 Optional对象,但 value 为空时返回 Optional.empty()。推荐使用
4. get()
返回 Optional中包装的值,在判空之前,千万不要直接使用!尽量别用!
5. orElse(T other)
同样是返回 Optional中包装的值,但不同的是当取不到值时,返回你指定的 default。看似很好,但不建议用
6. orElseGet(Supplier<? extends T> other)
同样是返回 Optional中包装的值,取不到值时,返回你指定的 default。看似和 5 一样,但推荐使用
7. orElseThrow(Supplier<? extends X> exceptionSupplier)
返回 Optional中包装的值,取不到值时抛出指定的异常。阻塞性业务场景推荐使用
8. isPresent()
判断 Optional中是否有值,返回 boolean,某些情况下很有用,但尽量不要用在 if 判断体中。可以用
9. ifPresent(Consumer<? super T> consumer)
判断 Optional中是否有值,有值则执行 consumer,否则什么都不干。日常情况下请使用这个
TIPS
首先是一些基本原则:
- 不要声明任何Optional实例属性
- 不要在任何 setter 或者构造方法中使用Optional
- Optional属于返回类型,在业务返回值或者远程调用中使用
1. 业务上需要空值时,不要直接返回 null,使用Optional.empty()
public Optional<User> getUser(String name) {
if (StringUtil.isNotEmpty(name)) {
return RemoteService.getUser(name);
}
return Optional.empty();
}
2. 使用 orElseGet()
获取 value 有三种方式:get() orElse() orElseGet()。这里推荐在需要用到的地方只用 orElseGet()。
首先,get()不能直接使用,需要结合判空使用。这和 !=null其实没多大区别,只是在表达和抽象上有所改善。
其次,为什么不推荐 orElse()呢?因为 orElse()无论如何都会执行括号中的内容, orElseGet()只在主体 value 是空时执行,下面看个例子:
public String getName() {
System.out.print("method called");
}
String name1 = Optional.of("String").orElse(getName()); //output: method called
String name2 = Optional.of("String").orElseGet(() -> getName()); //output:
如果上面的例子 getName()方法是一个远程调用,或者涉及大量的文件 IO,代价可想而知。
但 orElse()就一无是处吗?并不是。orElseGet()需要构建一个 Supplier,如果只是简单的返回一个静态资源、字符串等等,直接返回静态资源即可。
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {
Optional<String> status = ... ; //
return status.orElse(USER_STATUS);
}
//不要这么写
public String findUserStatus(long id) {
Optional<String> status = ... ; //
return status.orElse("UNKNOWN");//这样每次都会新建一个String对象
}
3. 使用 orElseThrow()
这个针对阻塞性的业务场景比较合适,例如没有从上游获取到用户信息,下面的所有操作都无法进行,那此时就应该抛出异常。正常的写法是先判空,再手动 throw 异常,现在可以集成为一行:
public String findUser(long id) {
Optional<User> user = remoteService.getUserById(id) ;
return user.orElseThrow(IllegalStateException::new);
}
4. 不为空则执行时,使用 ifPresent()
这点没有性能上的优势,但可以使代码更简洁:
//之前是这样的
if (status.isPresent()) {
System.out.println("Status: " + status.get());
}
//现在
status.ifPresent(System.out::println);
5. 不要滥用
有些简单明了的方法,完全没必要增加 Optional来增加复杂性。
public String fetchStatus() {
String status = getStatus() ;
return Optional.ofNullable(status).orElse("PENDING");
}
//判断一个简单的状态而已
public String fetchStatus() {
String status = ... ;
return status == null ? "PENDING" : status;
}
首先,null 可以作为集合的元素之一,它并不是非法的;其次,集合类型本身已经具备了完整的空表达,再去包装一层 Optional也是徒增复杂,收益甚微。例如,map 已经有了 getOrDefault()这样的类似 orElse()的 API 了。
总结
Optional的出现使 Java 对 null 的表达能力更近了一步,好马配好鞍,合理使用可以避免大量的 NPE,节省大量的人力物力。以上内容也是本人查询了很多资料,边学边写的产出,如有错漏之处,还请不吝指教。
如果此文对你有所帮助,希望能随手点个转发!
相关推荐
- 教你把多个视频合并成一个视频的方法
-
一.情况介绍当你有一个m3u8文件和一个目录,目录中有连续的视频片段,这些片段可以连成一段完整的视频。m3u8文件打开后像这样:m3u8文件,可以理解为播放列表,里面是播放视频片段的顺序。视频片段像这...
- 零代码编程:用kimichat合并一个文件夹下的多个文件
-
一个文件夹里面有很多个srt字幕文件,如何借助kimichat来自动批量合并呢?在kimichat对话框中输入提示词:你是一个Python编程专家,完成如下的编程任务:这个文件夹:D:\downloa...
- Java APT_java APT 生成代码
-
JavaAPT(AnnotationProcessingTool)是一种在Java编译阶段处理注解的工具。APT会在编译阶段扫描源代码中的注解,并根据这些注解生成代码、资源文件或其他输出,...
- Unit Runtime:一键运行 AI 生成的代码,或许将成为你的复制 + 粘贴神器
-
在我们构建了UnitMesh架构之后,以及对应的demo之后,便着手于实现UnitMesh架构。于是,我们就继续开始UnitRuntime,以用于直接运行AI生成的代码。PS:...
- 挣脱臃肿的枷锁:为什么说Vert.x是Java开发者手中的一柄利剑?
-
如果你是一名Java开发者,那么你的职业生涯几乎无法避开Spring。它如同一位德高望重的老国王,统治着企业级应用开发的大片疆土。SpringBoot的约定大于配置、SpringCloud的微服务...
- 五年后,谷歌还在全力以赴发展 Kotlin
-
作者|FredericLardinois译者|Sambodhi策划|Tina自2017年谷歌I/O全球开发者大会上,谷歌首次宣布将Kotlin(JetBrains开发的Ja...
- kotlin和java开发哪个好,优缺点对比
-
Kotlin和Java都是常见的编程语言,它们有各自的优缺点。Kotlin的优点:简洁:Kotlin程序相对于Java程序更简洁,可以减少代码量。安全:Kotlin在类型系统和空值安全...
- 移动端架构模式全景解析:从MVC到MVVM,如何选择最佳设计方案?
-
掌握不同架构模式的精髓,是构建可维护、可测试且高效移动应用的关键。在移动应用开发中,选择合适的软件架构模式对项目的可维护性、可测试性和团队协作效率至关重要。随着应用复杂度的增加,一个良好的架构能够帮助...
- 颜值非常高的XShell替代工具Termora,不一样的使用体验!
-
Termora是一款面向开发者和运维人员的跨平台SSH终端与文件管理工具,支持Windows、macOS及Linux系统,通过一体化界面简化远程服务器管理流程。其核心定位是解决多平台环境下远程连接、文...
- 预处理的底层原理和预处理编译运行异常的解决方案
-
若文章对您有帮助,欢迎关注程序员小迷。助您在编程路上越走越好![Mac-10.7.1LionIntel-based]Q:预处理到底干了什么事情?A:预处理,顾名思义,预先做的处理。源代码中...
- 为“架构”再建个模:如何用代码描述软件架构?
-
在架构治理平台ArchGuard中,为了实现对架构的治理,我们需要代码+模型描述所要处理的内容和数据。所以,在ArchGuard中,我们有了代码的模型、依赖的模型、变更的模型等,剩下的两个...
- 深度解析:Google Gemma 3n —— 移动优先的轻量多模态大模型
-
2025年6月,Google正式发布了Gemma3n,这是一款能够在2GB内存环境下运行的轻量级多模态大模型。它延续了Gemma家族的开源基因,同时在架构设计上大幅优化,目标是让...
- 比分网开发技术栈与功能详解_比分网有哪些
-
一、核心功能模块一个基本的比分网通常包含以下模块:首页/总览实时比分看板:滚动展示所有正在进行的比赛,包含比分、比赛时间、红黄牌等关键信息。热门赛事/焦点战:突出显示重要的、关注度高的比赛。赛事导航...
- 设计模式之-生成器_一键生成设计
-
一、【概念定义】——“分步构建复杂对象,隐藏创建细节”生成器模式(BuilderPattern):一种“分步构建型”创建型设计模式,它将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建...
- 构建第一个 Kotlin Android 应用_kotlin简介
-
第一步:安装AndroidStudio(推荐IDE)AndroidStudio是官方推荐的Android开发集成开发环境(IDE),内置对Kotlin的完整支持。1.下载And...
- 一周热门
-
-
【验证码逆向专栏】vaptcha 手势验证码逆向分析
-
Psutil + Flask + Pyecharts + Bootstrap 开发动态可视化系统监控
-
一个解决支持HTML/CSS/JS网页转PDF(高质量)的终极解决方案
-
再见Swagger UI 国人开源了一款超好用的 API 文档生成框架,真香
-
网页转成pdf文件的经验分享 网页转成pdf文件的经验分享怎么弄
-
C++ std::vector 简介
-
飞牛OS入门安装遇到问题,如何解决?
-
系统C盘清理:微信PC端文件清理,扩大C盘可用空间步骤
-
10款高性能NAS丨双十一必看,轻松搞定虚拟机、Docker、软路由
-
python使用fitz模块提取pdf中的图片
-
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)
- mysql刷新权限 (34)