一、前言

如果要给 Java 所有异常弄个榜单,我会选择将 NullPointerException 放在榜首。这个异常潜伏在代码中,就像个遥控炸弹,不知道什么时候这个按钮会被突然按下(传入 null 对象)。

还记得刚入行程序员的时候,三天两头碰到空指针异常引发的 Bug,解决完一个,又在另一处碰到。那时候师兄就教我,不要相信任何『对象』,特别是别人给你的,这些地方都加上判断。于是代码通常为会变成下面这样:

if(obj!=null){
// do something
}

有了这个防御之后,虽然不用再担心空指针异常,但是过多的判断语句使得代码变得臃肿。

假设我们存在如下对象关系

706 x 454

原本为了获取图中的 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

Optional.of 与 Optional.ofNullable

Optional 本质是一个容器,需要我们将对象实例传入该容器中。Optional 的构造方法为 private,无法直接使用 new 构建对象,只能使用 Optional 提供的静态方法创建。

Optional 三个创建方法如下:

  • Optional.of(obj),如果对象为 null,将会抛出 NPE。
  • Optional.ofNullable(obj),如果对象为 null,将会创建不包含值的 empty Optional 对象实例。
  • Optional.empty() 等同于 Optional.ofNullable(null)

624 x 386

只有在确定对象不会为 null 的情况使用 Optional#of,否则建议使用 Optional#ofNullable 方法。

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,下面 Optional API 你将会很熟悉。

Optional.ifPresent

通常情况下,空指针检查之后,如果对象不为空,将会进行下一步处理,比如打印该对象。

Company company = ...;
if(company!=null){
System.out.println(company);
}

上面代码我们可以使用 Optional#ifPresent 代替,如下所示:

Optional<Company> optCompany = ...;
optCompany.ifPresent(System.out::println);

使用 ifPresent 方法,我们不用再显示的进行检查,如果 Optional 为空,上面例子将不再输出。

Optional.ifPresentOrElse

java.util.Optional类的ifPresentOrElse(Consumer,Runnable)方法可帮助我们执行此Optional对象的值的指定消费者操作。如果此Optional中不存在值,则此方法执行给定的基于空的Runnable emptyAction,作为第二个参数传递

用法:

public void ifPresentOrElse(Consumer<T> action,
Runnable emptyAction)

参数:此方法接受两个参数:

  • action:如果存在值,则是对此Optional所执行的操作。
  • emptyAction:如果不存在任何值,这是要执行的基于空的操作。

返回值:此方法不返回任何内容。

异常:如果存在一个值并且给定的操作为null,或者不存在任何值并且给定的基于空的操作为null,则此方法抛出NullPointerException。

注意:由于此方法是在Java 9中添加的,因此程序需要JDK 9才能执行。

示例:

// Java program to demonstrate
// Optional.ifPresentOrElse method import java.util.*; public class GFG { public static void main(String[] args)
{ // create a Optional
Optional<Integer> op
= Optional.empty(); // print value
System.out.println("Optional: "
+ op); try { // apply ifPresentOrElse
op.ifPresentOrElse(
(value)
-> { System.out.println(
"Value is present, its: "
+ value); },
()
-> { System.out.println(
"Value is empty"); });
}
catch (Exception e) {
System.out.println(e);
}
}
}

输出:

Optional: Optional.empty
Value is empty

Optional or()

如果存在任何值,则使用Java中java.util.Optional类的or()方法来获取此Optional实例。如果此Optional实例中不存在任何值,则此方法返回一个Optional实例,其中包含从指定供应商生成的值。

用法:

public Optional<T> or(Supplier<T> supplier)

参数:此方法接受供应商作为类型T的参数,以使用从指定供应商生成的值来生成Optional实例。

Return supplier:如果存在任何值,则此方法返回此Optional实例。如果此Optional实例中不存在任何值,则此方法返回一个Optional实例,其中包含从指定供应商生成的值。

异常:如果提供的函数为null或产生空结果,则此方法引发NullPointerException。

以下示例程序旨在说明or()方法:

注意:由于此方法是在Java 9中添加的,因此程序需要JDK 9才能执行。

程序1:

// Java program to demonstrate
// Optional.or() method import java.util.*;
import java.util.function.*; public class GFG { public static void main(String[] args)
{ // create a Optional
Optional<Integer> op
= Optional.of(9455); // print supplier
System.out.println("Optional: "
+ op); // or supplier
System.out.println("Optional by or(() ->"
+ " Optional.of(100)) method: "
+ op.or(() -> Optional.of(100)));
}
}

输出:

Optional: Optional[9455]
Optional by or(() -> Optional.of(100)) method: Optional[9455]

程序2:

// Java program to demonstrate
// Optional.or() method import java.util.*;
import java.util.function.*; public class GFG { public static void main(String[] args)
{ // create a Optional
Optional<Integer> op
= Optional.empty(); // print supplier
System.out.println("Optional: "
+ op); try { // or supplier
System.out.println("Optional by or(() ->"
+ " Optional.of(100)) method: "
+ op.or(() -> Optional.of(100)));
}
catch (Exception e) {
System.out.println(e);
}
}
}

输出:

Optional: Optional.empty
Optional by or(() -> Optional.of(100)) method: Optional[100]

参考: https://docs.oracle.com/javase/9/docs/api/java/util/Optional.html#or-java.util.function.Supplier-

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

Optional.orElse 与 Optional.orElseThrow

当一个对象为 null 时,业务上通常可以设置一个默认值,从而使流程继续下去。

String name = company != null ? company.getName() : "Unknown";

或者抛出一个内部异常,记录失败原因,快速失败。

if (company.getName() == null) {
throw new RuntimeException();
}

Optional 类提供两个方法 orElseorElseThrow ,可以方便完成上面转化。

// 设置默认值
String name=companyOpt.orElse(new Company("Unknown")).getName(); // 抛出异常
String name=companyOpt.orElseThrow(RuntimeException::new).getName();

如果 Optional 为空,提供默认值或抛出异常。

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 方法,两者区别见下图。

728 x 601 902 x 745

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 表达式,使代码更加流畅连贯,并且提高代码整体可读性。

四、Reference

1、Tired of Null Pointer Exceptions? Consider Using Java SE 8's Optional!

2、Optionals: Patterns and Good Practices

3、Java8 in Action

【Java 8】Optional 使用的更多相关文章

  1. JAVA 8 Optional类介绍及其源码

    什么是Optional对象 Java 8中所谓的Optional对象,即一个容器对象,该对象可以包含一个null或非null值.如果该值不为null,则调用isPresent()方法将返回true,且 ...

  2. Java 8 Optional 类

    转自:https://www.runoob.com/java/java8-optional-class.html Optional 类是一个可以为null的容器对象.如果值存在则isPresent() ...

  3. Java中Optional类的使用

    从 Java 8 引入的一个很有趣的特性是 Optional  类.Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException) —— 每个 Java 程序员都 ...

  4. Java 8 新特性-菜鸟教程 (6) -Java 8 Optional 类

    Java 8 Optional 类 Optional 类是一个可以为null的容器对象.如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象. Optional 是个容 ...

  5. jdk8新特性:在用Repository实体查询是总是提示要java.util.Optional, 原 Inferred type 'S' for type parameter 'S' is not within its bound;

    jdk8新特性:在用Repository实体查询是总是提示要java.util.Optional 在使用springboot 方法报错: Inferred type 'S' for type para ...

  6. java中Optional和Stream流的部分操作

    package test9; import java.util.DoubleSummaryStatistics; import java.util.Optional; import java.util ...

  7. Java 8 Optional In Depth

    OptionalBasicExample.java package com.mkyong; import java.util.Optional; public class OptionalBasicE ...

  8. Java 8 Optional 类深度解析

    Java 8 Optional 类深度解析 身为一名Java程序员,大家可能都有这样的经历:调用一个方法得到了返回值却不能直接将返回值作为参数去调用别的方法.我们首先要判断这个返回值是否为null,只 ...

  9. Java 8 Optional类使用的实践经验

    前言 Java中空指针异常(NPE)一直是令开发者头疼的问题.Java 8引入了一个新的Optional类,使用该类可以尽可能地防止出现空指针异常. Optional 类是一个可以为null的容器对象 ...

  10. Java 8 中的 java.util.Optional

    Java 8 中的 java.util.Optional 学习了:https://blog.csdn.net/sun_promise/article/details/51362838 package ...

随机推荐

  1. Linux mem 2.6 Rmap 内存反向映射机制

    文章目录 1. 简介 2. 匿名内存 Rmap 的建立 2.1 fork() 2.2 do_page_fault() 3. 文件内存 Rmap 的建立 3.1 fork() 3.2 do_page_f ...

  2. pytest-mian函数运行

    1.设置多并发运行 1.命令行安装 pip install pytest-xdist #安装插件,进行多并发运行,#调用:-n -5 import pytest # pytest.main([&quo ...

  3. <C#任务导引教程>练习八

    //66,静态成员与非静态成员的访问方式using System;class Text{    public int x;    public static int y;//静态数据成员    voi ...

  4. Javascript复制内容到剪贴板,解决navigator.clipboard Cannot read property 'writeText' of undefined

    起因 最近帮同事实现了一个小功能--复制文本到剪贴板,主要参考了前端大神阮一峰的博客,根据 navigator.clipboard 返回的 Clipboard 对象的方法 writeText() 写文 ...

  5. Web优化躬行记(5)——网站优化

    最近阅读了很多优秀的网站性能优化的文章,所以自己也想总结一些最近优化的手段和方法. 个人感觉性能优化的核心是:减少延迟,加速展现. 本文主要从产品设计.前端.后端和网络四个方面来诉说优化过程. 一.产 ...

  6. [uoj272]石家庄的工人阶级队伍比较坚强

    假设$x,y\in \{0,1,2\}$,则$x$能赢$y$(根据题中定义)当且仅当$x-y\equiv 1(mod\ 3)$ 定义$\ominus$为两数3进制下不退位的减法,$S_{x}$表示$x ...

  7. 实践案例1-利用低代码开发平台Odoo快速构建律师事务所管理系统

    今年10月份中旬的时候,有一段时间没联系的中学同学,我跟他关系比较好,突然打电话给我,希望我给他夫人的律所开发一个小系统.记得十几年前,当他还在他叔叔公司上班的,他是负责销售的,我们几乎每周都碰面,那 ...

  8. 我在组内的Java问题排查分享

    前言 最近翻看以前写的 PPT, 发现了在2019年做的一次技术分享,关于 Java 问题排查,由于没什么公司机密可言,整理下分享给大家~ 线上问题处理流程 直接放PPT截图吧,现在看来依然不过时 问 ...

  9. dart系列之:数学什么的就是小意思,看我dart如何玩转它

    目录 简介 dart:math包的构成 math Random 总结 简介 dart也可以进行数学运算,dart为数学爱好者专门创建了一个dart:math包来处理数学方面的各种操作.dart:mat ...

  10. zabbix_get

    zabbix_get是一个命令行工具,可用于从一个远程的Zabbix探针获取监控数据. 1.2 用法 zabbix_get [-hV] [-s <host name or IP> ] [- ...