Java8中Optional的基础使用和实践
说明
首先我们来看一下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
直接使用 isPresent() 进行 if 检查
这个直接参考上面的例子,用if判断和 1.8 之前的写法并没有什么区别,反而返回值包了一层Optional,增加了代码的复杂性,没有带来任何实质的收益。其实isPresent()一般用于流处理的结尾,用于判断是否符合条件。
list.stream().filer(x -> Objects.equals(x,param)).findFirst().isPresent()
在方法参数中使用 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
}
直接使用 Optional.get:
Optional不会帮你做任何的空判断或者异常处理,如果直接在代码中使用Optional.get()和不做任何空判断一样,十分危险。这种可能会出现在那种所谓的着急上线,着急交付,对Optional也不是很熟悉,直接就用了。这里多说一句,可能有人会反问了:甲方/业务着急,需求又多,哪有时间给他去做优化啊?因为我在现实工作中遇到过,但这两者并不矛盾,因为代码行数上差别并不大,只要自己平时保持学习,都是信手拈来的东西。
使用在 POJO 中
估计很少有人这么用:
public class User {private int age;private String name;private Optional<String> address;
}
这样的写法将会给序列化带来麻烦,Optional本身并没有实现序列化,现有的 JSON 序列化框架也没有对此提供支持的。
使用在注入的属性中
这种写法估计用的人会更少,但不排除有脑洞的。
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。
empty()
返回一个Optional容器对象,而不是 null。建议常用⭐⭐⭐⭐
of(T value)
创建一个Optional对象,如果 value 是 null,则抛出 NPE。不建议用⭐⭐
ofNullable(T value)
同上,创建一个Optional对象,但 value 为空时返回Optional.empty()。推荐使用⭐⭐⭐⭐⭐
get()
返回Optional中包装的值,在判空之前,千万不要直接使用!尽量别用!⭐
orElse(T other)
同样是返回Optional中包装的值,但不同的是当取不到值时,返回你指定的 default。看似很好,但不建议用⭐⭐
orElseGet(Supplier<? extends T> other)
同样是返回Optional中包装的值,取不到值时,返回你指定的 default。看似和 5 一样,但推荐使用⭐⭐⭐⭐⭐
orElseThrow(Supplier<? extends X> exceptionSupplier)
返回Optional中包装的值,取不到值时抛出指定的异常。阻塞性业务场景推荐使用⭐⭐⭐⭐
isPresent()
判断Optional中是否有值,返回 boolean,某些情况下很有用,但尽量不要用在 if 判断体中。可以用⭐⭐⭐
ifPresent(Consumer<? super T> consumer)
判断Optional中是否有值,有值则执行 consumer,否则什么都不干。日常情况下请使用这个⭐⭐⭐⭐
TIPS
首先是一些基本原则:
- 不要声明任何Optional实例属性
- 不要在任何 setter 或者构造方法中使用Optional
- Optional属于返回类型,在业务返回值或者远程调用中使用
业务上需要空值时,不要直接返回 null,使用Optional.empty()
public Optional<User> getUser(String name) {if (StringUtil.isNotEmpty(name)) {return RemoteService.getUser(name);} return Optional.empty();
}
使用 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对象
}
使用 orElseThrow()
这个针对阻塞性的业务场景比较合适,例如没有从上游获取到用户信息,下面的所有操作都无法进行,那此时就应该抛出异常。正常的写法是先判空,再手动 throw 异常,现在可以集成为一行:
public String findUser(long id) {Optional<User> user = remoteService.getUserById(id) ;return user.orElseThrow(IllegalStateException::new);
}
不为空则执行时,使用 ifPresent()
这点没有性能上的优势,但可以使代码更简洁:
//之前是这样的
if (status.isPresent()) {System.out.println("Status: " + status.get());
}//现在
status.ifPresent(System.out::println);
不要滥用
有些简单明了的方法,完全没必要增加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,节省大量的人力物力。以上内容也是本人查询了很多资料,边学边写的产出,如有错漏之处,还请不吝指教。
以上为个人经验,希望能给大家一个参考,如有错误或未考虑完全的地方,望不吝赐教!
Java8中Optional的基础使用和实践相关推荐
- java8中Optional的使用
1.Optional对象的创建 创建空的Optional,Optional<T> optT = Optional.empty(),其中T为需要创建的类型,根据实际需要填写 创建非空值的Op ...
- 计算机应用基础教学实践活动,计算机应用基础与中职数学课程融合教学实践.doc...
计算机应用基础与中职数学课程融合教学实践.doc PAGE \* MERGEFORMAT 7 计算机应用基础与中职数学课程融合教学实践--以数据的图示单元教学为例吴晓进[摘 要] 中职<数学&g ...
- Java基础之Java8中Map的compute的使用
Java基础之Java8中Map的compute的使用 一.介绍 Java8更新后,Map接口中提供了compute方法.下面我们先看看官方文档的对它的使用说明. 如果看完上面的还是不太明白的话,看下 ...
- 计算机应用基础问卷答题,中职计算机应用基础课程教学改革与实践
中职计算机应用基础课程教学改革与实践 来源:用户上传 作者: 雷远胜 摘 要:分析职校一年级新生的实际学习情况,从明确学习目标,掌握正确学习方法:通过问卷调查和摸底测试,了解职校学生实际的计算机水平: ...
- Java基础之Java8中map和flatMap的使用
Java基础之Java8中map和flatMap的使用 一.介绍 首先,看下map和flatMap的官方文档说明 map flatMap 其实单纯的看api说明还是比较抽象,下面我将以几个实战例子来帮 ...
- 《大数据系统基础》课程实践项目中期答辩顺利举行,清华持续探索大数据人才教育创新之路
2017年11月15日,清华大学大数据能力提升项目之<大数据系统基础>课程实践项目中期答辩在清华大学六号教学楼顺利举行.160余名同学分为21组,向任课老师和企业导师汇报了各组实践项目的进 ...
- 教你怎么样在 Java8 中优雅的避开空指针异常
2019独角兽企业重金招聘Python工程师标准>>> 要说 Java 编程中哪个异常是你印象最深刻的,那 NullPointerException 空指针可以说是臭名昭著的.不要说 ...
- Java8中的HashMap分析
本篇文章是网上多篇文章的精华的总结,结合自己看源代码的一些感悟,其中线程安全性和性能测试部分并未做实践测试,直接是"拿来"网上的博客的. 哈希表概述 哈希表本质上一个数组,数组中每 ...
- Java8中的流操作-基本使用性能测试
一.流(Stream)简介 流是 Java8 中 API 的新成员,它允许你以声明式的方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现).这有点儿像是我们操作数据库一样,例如我想要查询出热 ...
最新文章
- stn专线和otn有什么区别_专线与干线运输的区别是什么?
- 在MFC里面实现线程的实例
- linux网络编程之用一张图片说明套接口常用函数
- 使用Hazelcast发布和订阅
- html属性可以用来定义内联样式,18年6月考试《网页设计与制作》期末大作业.doc...
- client 连接 host —— 虚拟机
- jsp获取连接池的实时连接数_一篇看懂数据库连接池概念、原理、运行机制
- 吴恩达机器学习 6.神经网络学习
- 使用数据绑定实现多窗口间的数据同步
- 打印服务器自动关闭,win7打印机服务print spooler老是自动关闭怎么解决
- DEA博弈交叉效率matlab,基于博弈交叉效率DEA模型的服务模块化创新绩效测量—以江西省文化创意产业为例...
- Mac屏幕分辨率如何更改?
- python画正弦函数_python中怎么用matlibplot画正弦曲线
- 谷歌浏览器如何设置internet选项
- 基于STM32的光照度自动调节系统
- 决策树与随机森林(从入门到精通)
- Java Web概述-练习题
- Qt 开发ARM64程序
- MADlib——基于SQL的数据挖掘解决方案(23)——分类之SVM
- 硬盘提示初始化是什么意思?数据会丢失吗?
热门文章
- SpringCloud之Eureka实战和架构设计解析
- 我眼中的《芳华》读后感作文2500字
- tensorflow serving部署keras或tf2.0模型
- 华清远见-重庆中心-数据库阶段技术总结/知识点梳理/个人总结
- 【ACM- OJ】《Oulipo》C++
- 独立显卡linux安装教程,linux 安装meshlab并且指定使用独立显卡
- C语言编程>第一周 ③ 输入某年某月某日,判断这一天是这一年的第几天
- 魔点人脸识别闸机系统落地郑州中心客运站
- 2019百度地图离线地图制作
- php老师把毕生的功力,为了表现雨下的凶残程度,网友都使出了毕生的功力