作者 | ES_her0

来源 | https://xie.infoq.cn/article/e3d1f0f4f095397c44812a5be

很多公众号其实都发过 Optional 的文章, 但大多文章都是介绍了 Optional 的 API 用法,却没有给出怎么正确的使用 Optional,这可能会误导一部分小白使用者,私以为,在项目中一知半解的使用 Optional,我更愿意看到老老实实的 null 判断。今天我给大家分享的这篇文章,便是 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,节省大量的人力物力。以上内容也是本人查询了很多资料,边学边写的产出,如有错漏之处,还请不吝指教。

往期推荐

令人笑喷的56个代码注释,你写过多少?

还在用 Random生成随机数?试试 ThreadLocalRandom,超好用!

消息幂等(去重)通用解决方案,真顶!

最强代码生成器平台,杀疯了!

Spring为什么建议使用构造器来注入?

喜欢本文欢迎转发,关注我订阅更多精彩

关注我回复「加群」,加入Spring技术交流群

Java8 Optional 最佳实践相关推荐

  1. Optional 最佳实践

    目录 一 Optional 是什么 二 Optional API介绍 三 Optional 最佳实践 3.1 不要直接返回 null,使用 Optional.empty(): 3.2 正确使用 ifP ...

  2. Java8语法最佳实践-什么是对象

    第一章 对象的概念 计算机革命起源机器.编程语言就像是那台机器.它不仅是我们思维放大的工具与另一种表达媒介,更像是我们思想的一部分.语言的灵感来自其他形式的表达,如写作,绘画,雕塑,动画和电影制作.编 ...

  3. java微妙_10个微妙的Java编码最佳实践

    编写和维护jOOQ(Java中内部DSL建模的SQL)时遇到过这些.作为一个内部DSL,jOOQ最大限度的挑战了Java的编译器和泛型,把泛型,可变参数和重载结合在一起,Josh Bloch可能不会推 ...

  4. 10个精妙的Java编码最佳实践

    这是一个比Josh Bloch的Effective Java规则更精妙的10条Java编码实践的列表.和Josh Bloch的列表容易学习并且关注日常情况相比,这个列表将包含涉及API/SPI设计中不 ...

  5. Java 的最佳实践

    Java 是在世界各地最流行的编程语言之一, 但是看起来没人喜欢使用它.而 Java 事实上还算是一门不错的语言,随着 Java 8 最近的问世,我决定编制一个库,实践和工具的清单,汇集 Java 的 ...

  6. Java 设计模式最佳实践:1~5

    原文:Design Patterns and Best Practices in Java 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自[ApacheCN Java 译文集],采用译后编 ...

  7. Spring Batch在大型企业中的最佳实践

    在大型企业中,由于业务复杂.数据量大.数据格式不同.数据交互格式繁杂,并非所有的操作都能通过交互界面进行处理.而有一些操作需要定期读取大批量的数据,然后进行一系列的后续处理.这样的过程就是" ...

  8. Java8 Optional,可以这样用啊

    以下文章来源方志朋的博客,回复"666"获面试宝典 很多公众号其实都发过 Optional 的文章, 但大多文章都是介绍了 Optional 的 API 用法,却没有给出怎么正确的 ...

  9. Code Review最佳实践

    我一直认为Code Review(代码审查)是软件开发中的最佳实践之一,可以有效提高整体代码质量,及时发现代码中可能存在的问题.包括像Google.微软这些公司,Code Review都是基本要求,代 ...

最新文章

  1. JS改变input的value值不触发onchange事件解决方案 (转)
  2. 『Numpy』np.ravel()和np.flatten()
  3. 1~9组成三个3位的平方数
  4. python3中多项式创建_机器学习入门之机器学习之路:python 多项式特征生成PolynomialFeatures 欠拟合与过拟合...
  5. sqlserver 多表更新
  6. dynamic与var
  7. 计算机怎样辅助英语听力教学方法有哪些,计算机辅助教学在英语听力中的运用.doc...
  8. Tomcat 内存调大
  9. (15)HTML面试题集锦
  10. USACO / Factorials (简单模拟)
  11. 计算机快捷键下档健,电脑文档快捷键
  12. OSG——- 对点选物体平移(鼠标点选物体、物体随鼠标移动、屏幕坐标转世界坐标)
  13. Mac 下安装Redis
  14. Leap Motion 上手体验
  15. python一切皆对象的理解_Python难点解析---初级篇2.一切皆对象
  16. 一个普普通通大四学生的2021
  17. 【Fiddler】从零开始学习Fiddler
  18. MySQL数据库基础
  19. Java Web应用程序开发
  20. Modbus转Profinet网关与ARX-MA100微型空气质量监测系统配置案例

热门文章

  1. 查看Linux进程状态
  2. docker build 没有网络 执行dockerfile中yum报错 curl#6 - “Could not resolve host: mirrorlist.centos.org 解决方法
  3. python pymysql 多线程 读写数据库 报错 Packet sequence number wrong
  4. c语言printf相关函数 格式化字符串攻击 简介
  5. linux cron crontab anacron 计划任务 定时任务
  6. python3 uuid模块
  7. linux 内核编译错误 undefined reference to '__mutex_lock_slowpath'
  8. 关于Fuzz工具的那些事儿
  9. VS2013编译Duilib界面库,“找不到Riched20.lib”的问题
  10. 二维指针动态分配内存连续问题分析