若是要给 Java 全部异常弄个榜单,我会选择将 NullPointerException
放在榜首。这个异常潜伏在代码中,就像个遥控炸弹,不知道何时这个按钮会被忽然按下(传入 null 对象)。html
还记得刚入行程序员的时候,三天两头碰到空指针异常引起的 Bug,解决完一个,又在另外一处碰到。那时候师兄就教我,不要相信任何『对象』,特别是别人给你的,这些地方都加上判断。因而代码一般为会变成下面这样:java
if(obj!=null){
// do something
}
复制代码
有了这个防护以后,虽然不用再担忧空指针异常,可是过多的判断语句使得代码变得臃肿。程序员
假设咱们存在以下对象关系安全
本来为了获取图中的 name
属性,本来一句代码就能够轻松完成。oracle
Staff staff=..;
staff.getDepartment().getCompany().getName();
复制代码
可是很不幸,为了代码的安全性,咱们不得不加入空指针判断代码。ide
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 语句下降代码总体可读性,提升复杂度。idea
所幸 Java 8 引入引入一个新类 Java.util.Optional<T>
,依靠 Optional 类提供 API,咱们能够写出既安全又具备阅读性的代码。spa
还在使用 JDK 6 ?那你也别急着关闭这篇文章。能够考虑使用 Guava Optional。不过须要注意的是,Guava Optional API 与 JDK 存在差别,如下以 JDK8 Optional 为例。3d
Optional 本质是一个容器,须要咱们将对象实例传入该容器中。Optional
的构造方法为 private
,没法直接使用 new 构建对象,只能使用 Optional
提供的静态方法建立。
Optional
三个建立方法以下:
Optional.of(obj)
,若是对象为 null,将会抛出 NPE。Optional.ofNullable(obj)
,若是对象为 null,将会建立不包含值的 empty Optional
对象实例。Optional.empty()
等同于 Optional.ofNullable(null)
只有在肯定对象不会为 null 的状况使用 Optional#of
,不然建议使用 Optional#ofNullable
方法。
对象实例存入 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,下面 Optional
API 你将会很熟悉。
一般状况下,空指针检查以后,若是对象不为空,将会进行下一步处理,好比打印该对象。
Company company = ...;
if(company!=null){
System.out.println(company);
}
复制代码
上面代码咱们可使用 Optional#ifPresent
代替,以下所示:
Optional<Company> optCompany = ...;
optCompany.ifPresent(System.out::println);
复制代码
使用 ifPresent
方法,咱们不用再显示的进行检查,若是 Optional
为空,上面例子将再也不输出。
有时候咱们须要某些属性知足必定条件,才进行下一步动做。这里假设咱们当 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
。
当一个对象为 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
为空,提供默认值或抛出异常。
熟悉 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<Staff> staffOpt =...;
staffOpt
.flatMap(Staff::getDepartment)
.flatMap(Department::getCompany)
.map(Company::getName)
.orElse("Unknown");
复制代码
能够看到重构以后代码利用 Optional
的 Fluent Interface,以及 lambda 表达式,使代码更加流畅连贯,而且提升代码总体可读性。
一、Tired of Null Pointer Exceptions? Consider Using Java SE 8's Optional!
三、Optionals: Patterns and Good Practices
三、Java8 in Action
欢迎关注个人公众号:程序通事,得到平常干货推送。若是您对个人专题内容感兴趣,也能够关注个人博客:studyidea.cn