还在重复写空指针检查代码?考虑使用 Optional 吧!
一、前言
如果要给 Java 所有异常弄个榜单,我会选择将
NullPointerException放在榜首。这个异常潜伏在代码中,就像个遥控炸弹,不知道什么时候这个按钮会被突然按下(传入 null 对象)。
还记得刚入行程序员的时候,三天两头碰到空指针异常引发的 Bug,解决完一个,又在另一处碰到。那时候师兄就教我,不要相信任何『对象』,特别是别人给你的,这些地方都加上判断。于是代码通常为会变成下面这样:
if(obj!=null){ // do something }
有了这个防御之后,虽然不用再担心空指针异常,但是过多的判断语句使得代码变得臃肿。
假设我们存在如下对象关系
原本为了获取图中的
name属性,原本一句代码就可以轻松完成。
Staff staff=..; staff.getDepartment().getCompany().getName();
但是很不幸,为了代码的安全性,我们不得不加入空指针判断代码。
Staff staff=..; if (staff != null) { Department department = staff.getDepartment(); if (department != null) { Company company = department.getCompany(); if (company != null) { return company.getName(); } } } return "Unknown";
当其中对象为
null时,可以返回默认值,如上所示。也可以直接抛出其他异常快速失败。
虽然上面代码变得更加安全,但是过多嵌套 if 语句降低代码整体可读性,提高复杂度。
所幸 Java 8 引入引入一个新类
Java.util.Optional<T>,依靠 Optional 类提供 API,我们可以写出既安全又具有阅读性的代码。
还在使用 JDK 6 ?那你也别急着关闭这篇文章。可以考虑使用 Guava Optional。不过需要注意的是,Guava Optional API 与 JDK 存在差异,以下以 JDK8 Optional 为例。
二、Optional API
2.1、Optional#of 与 Optional#ofNullable
Optional 本质是一个容器,需要我们将对象实例传入该容器中。
Optional的构造方法为
private,无法直接使用 new 构建对象,只能使用
Optional提供的静态方法创建。
Optional三个创建方法如下:
Optional.of(obj)
,如果对象为 null,将会抛出 NPE。Optional.ofNullable(obj)
,如果对象为 null,将会创建不包含值的 emptyOptional
对象实例。Optional.empty()
等同于Optional.ofNullable(null)
只有在确定对象不会为 null 的情况使用
Optional#of,否则建议使用
Optional#ofNullable方法。
2.2、Optional#get 与 Optional#isPresent
对象实例存入
Optional容器中之后,最后我们需要从中取出。
Optional#get方法用于取出内部对象实例,不过需要注意的是,如果是 empty Optional 实例,由于容器内没有任何对象实例,使用
get方法将会抛出
NoSuchElementException异常。
为了防止异常抛出,可以使用
Optional#isPresent。这个方法将会判断内部是否存在对象实例,若存在则返回 true。
示例代码如下:
Optional<Company> optCompany = Optional.ofNullable(company); // 与直接使用空指针判断没有任何区别 if (optCompany.isPresent()) { System.out.println(optCompany.get().getName()); }
仔细对比,可以发现上面用法与空指针检查并无差别。刚接触到
Optional,看到很多文章介绍这个用法,那时候一直很疑惑,这个解决方案不是更加麻烦?
后来接触到
Optional其他 API,我才发现这个类真正有意义是下面这些 API。如果使用过 Java8 Stream 的 API,下面
OptionalAPI 你将会很熟悉。
2.3、Optional#ifPresent
通常情况下,空指针检查之后,如果对象不为空,将会进行下一步处理,比如打印该对象。
Company company = ...; if(company!=null){ System.out.println(company); }
上面代码我们可以使用
Optional#ifPresent代替,如下所示:
Optional<Company> optCompany = ...; optCompany.ifPresent(System.out::println);
使用
ifPresent方法,我们不用再显示的进行检查,如果
Optional为空,上面例子将不再输出。
2.4、Optional#filter
有时候我们需要某些属性满足一定条件,才进行下一步动作。这里假设我们当 Company name 属性为 Apple,打印输出 ok。
if (company != null && "Apple".equals(company.getName())) { System.out.println("ok"); }
下面使用
Optional#filter结合
Optional#ifPresent重写上面的代码,如下所示:
Optional<Company> companyOpt=...; companyOpt .filter(company -> "Apple".equals(company.getName())) .ifPresent(company -> System.out.println("ok"));
filter方法将会判断对象是否符合条件。如果不符合条件,将会返回一个空的
Optional。
2.5、Optional#orElse 与 Optional#orElseThrow
当一个对象为 null 时,业务上通常可以设置一个默认值,从而使流程继续下去。
String name = company != null ? company.getName() : "Unknown";
或者抛出一个内部异常,记录失败原因,快速失败。
if (company.getName() == null) { throw new RuntimeException(); }
Optional类提供两个方法
orElse与
orElseThrow,可以方便完成上面转化。
// 设置默认值 String name=companyOpt.orElse(new Company("Unknown")).getName(); // 抛出异常 String name=companyOpt.orElseThrow(RuntimeException::new).getName();
如果
Optional为空,提供默认值或抛出异常。
2.6、Optional#map 与 Optional#flatMap
熟悉 Java8 Stream 同学的应该了解,
Stream#map方法可以将当前对象转化为另外一个对象,
Optional#map方法也与之类似。
Optional<Company> optCompany = ...; Optional<String> nameopt = optCompany.map(Company::getName);
map 方法可以将原先
Optional<Company>转变成
Optional<String>,此时 Optional 内部对象变成 String 类型。如果转化之前
Optional对象为空,则什么也不会发生。
另外 Optional 还有一个
flatMap方法,两者区别见下图。
Department#getCompany返回对象为Optional<Company>
三、代码重构
上面我们学习了
Optional类主要 API ,下面我们使用
Optional重构文章刚开头的代码。为了方便读者对比,将上面的代码复制了下来。
代码重构前:
if (staff != null) { Department department = staff.getDepartment(); if (department != null) { Company company = department.getCompany(); if (company != null) { return company.getName(); } } } return "Unknown";
首先我们需要将
Staff,
Department修改 getter 方法返回结果类型改成
Optional。
public class Staff { private Department department; public Optional<Department> getDepartment() { return Optional.ofNullable(department); } ... } public class Department { private Company company; public Optional<Company> getCompany() { return Optional.ofNullable(company); } ... } public class Company { private String name; public String getName() { return name; } ... }
然后综合使用 Optional API 重构代码如下:
Optional<Sta 576 ff> staffOpt =...; staffOpt .flatMap(Staff::getDepartment) .flatMap(Department::getCompany) .map(Company::getName) .orElse("Unknown");
可以看到重构之后代码利用
Optional的 Fluent Interface,以及 lambda 表达式,使代码更加流畅连贯,并且提高代码整体可读性。
四、帮助文章
1、Tired of Null Pointer Exceptions? Consider Using Java SE 8's Optional!
3、Optionals: Patterns and Good Practices
3、Java8 in Action
欢迎关注我的公众号:程序通事,获得日常干货推送。如果您对我的专题内容感兴趣,也可以关注我的博客:studyidea.cn
- 使用clonedigger来检查python中的重复代码
- 检查端口是否被使用VC代码
- 可重复使用程序代码 + 可重复使用模型 = 更高的生产力
- Windows 64位机上C/C++代码静态检查工具Logiscope RuleChecker的安装和使用
- 在有源代码的情况下注释ComponentArt的许可证检查,彻底无风险使用ComponentArt
- 分享.NET开发中经常使用到的代码片段 完全从实际项目中提取出来,也可被反反复复的重复借用
- C/C++代码静态检查工具PC-lint在VS2008开发环境中的安装配置和使用
- C# 中使用不安全代码(unsafe、指针)实践
- tcl/tk脚本中使用大量重复代码的解决方法
- 使用Hudson和FindBugs进行持续集成和代码检查
- GFlags 检查内存越界、野指针等作用的工具使用
- 使用NDepend与LINQ检查代码
- 静态代码检查工具的使用(cppcheck)
- js检查页面上有无重复id的实现代码
- 使用cpplint进行提交代码的风格检查
- 使用Simian检查Java项目中冗余代码
- 使用asp_compiler.exe对aspx页面中的服务器端代码编译检查
- Python代码实现Java本地化资源字符串的检查,防止出现空指针异常
- 使用FxCop做代码检查和优化
- C# 托管代码 和 unsafe使用指针 对数组操作 性能比较--指针真的快么?