百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT知识 > 正文

Optional是个好东西,如果用错了就太可惜了

liuian 2025-07-14 18:27 3 浏览

原文出处:
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,节省大量的人力物力。以上内容也是本人查询了很多资料,边学边写的产出,如有错漏之处,还请不吝指教。


如果此文对你有所帮助,希望能随手点个转发!

相关推荐

Optional是个好东西,如果用错了就太可惜了

原文出处:https://xie.infoq.cn/article/e3d1f0f4f095397c44812a5be我们都知道,在Java8新增了一个类-Optional,主要是用来解决程...

IDEA建议:不要在字段上使用@Autowire了!

在使用IDEA写Spring相关的项目的时候,在字段上使用@Autowired注解时,总是会有一个波浪线提示:Fieldinjectionisnotrecommended.纳尼?我天天用,咋...

Spring源码|Spring实例Bean的方法

Spring实例Bean的方法,在AbstractAutowireCapableBeanFactory中的protectedBeanWrappercreateBeanInstance(String...

Spring技巧:深入研究Java 14和SpringBoot

在本期文章中,我们将介绍Java14中的新特性及其在构建基于SpringBoot的应用程序中的应用。开始,我们需要使用Java的最新版本,也是最棒的版本,Java14,它现在还没有发布。预计将于2...

Java开发200+个学习知识路线-史上最全(框架篇)

1.Spring框架深入SpringIOC容器:BeanFactory与ApplicationContextBean生命周期:实例化、属性填充、初始化、销毁依赖注入方式:构造器注入、Setter注...

年末将至,Java 开发者必须了解的 15 个Java 顶级开源项目

专注于Java领域优质技术,欢迎关注作者:SnailClimbStar的数量统计于2019-12-29。1.JavaGuideGuide哥大三开始维护的,目前算是纯Java类型项目中Sta...

字节跨平台框架 Lynx 开源:一个 Web 开发者的原生体验

最近各大厂都在开源自己的跨平台框架,前脚腾讯刚宣布计划四月开源基于Kotlin的跨平台框架「Kuikly」,后脚字节跳动旧开源了他们的跨平台框架「Lynx」,如果说Kuikly是一个面向...

我要狠狠的反驳“公司禁止使用Lombok”的观点

经常在其它各个地方在说公司禁止使用Lombok,我一直不明白为什么不让用,今天看到一篇文章列举了一下“缺点”,这里我只想狠狠地反驳,看到列举的理由我竟无言以对。原文如下:下面,结合我自己使用Lomb...

SpringBoot Lombok使用详解:从入门到精通(注解最全)

一、Lombok概述与基础使用1.1Lombok是什么Lombok是一个Java库,它通过注解的方式自动生成Java代码(如getter、setter、toString等),从而减少样板代码的编写,...

Java 8之后的那些新特性(六):记录类 Record Class

Java是一门面向对象的语言,而对于面向对象的语言中,一个众所周知的概念就是,对象是包含属性与行为的。比如HR系统中都会有雇员的概念,那雇员会有姓名,ID身份,性别等,这些我们称之为属性;而雇员同时肯...

为什么大厂要求安卓开发者掌握Kotlin和Jetpack?优雅草卓伊凡

为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡一、Kotlin:Android开发的现代语言选择1.1Kotlin是什么?Kotlin是由...

Kotlin这5招太绝了!码农秒变优雅艺术家!

Kotlin因其简洁性、空安全性和与Java的无缝互操作性而备受喜爱。虽然许多开发者熟悉协程、扩展函数和数据类等特性,但还有一些鲜为人知的特性可以让你的代码从仅仅能用变得真正优雅且异常简洁。让我们来看...

自行部署一款免费高颜值的IT资产管理系统-咖啡壶chemex

在运维时,ICT资产太多怎么办,还是用excel表格来管理?效率太低,也不好多人使用。在几个IT资产管理系统中选择比较中,最终在Snipe-IT和chemex间选择了chemex咖啡壶。Snip...

PHP对接百度语音识别技术(php对接百度语音识别技术实验报告)

引言在目前的各种应用场景中,语音识别技术已经越来越常用,并且其应用场景正在不断扩大。百度提供的语音识别服务允许用户通过简单的接口调用,将语音内容转换为文本。本文将通过PHP语言集成百度的语音识别服务,...

知识付费系统功能全解析(知识付费项目怎么样)

开发知识付费系统需包含核心功能模块,确保内容变现、用户体验及运营管理需求。以下是完整功能架构:一、用户端功能注册登录:手机号/邮箱注册,第三方登录(微信、QQ)内容浏览:分类展示课程、文章、音频等付费...