前言

在 Java 语言开发中,可能大多数程序员遇到最多的异常就是 NullPointException 空指针异常了。这个当初语言的开发者“仅仅因为这样实现起来更容易”而允许空引用所带来的代价是非常惨痛的。而我们开发者不得不使用多重 if 嵌套判断来规避 NPE 或者通过多个 if 结合 return 语句来终止程序。且看一个例子

假如需要处理下面的嵌套对象,这是一个用于汽车、汽车保险的客户。

public class Person {
private Car car;
public Car getCar() {
return car;
}
}
public class Car {
private Insurance insurance;
public Insurance getInsurance() {
return insurance;
}
}
public class Insurance {
private String name;
public String getName() {
return name;
}
}

那么下面的代码会存在怎样的问题呢?

public String getCarInsuranceNames(Person person) {
return person.getCar().getInsurance().getName();
}

没错,当这个人没有车 / 他的车没有上保险时,代码会抛出 NPE。或者说这个人根本就是 null,也会直接抛出异常。我们常见的作法就是在每次 get 方法之后,进行 if 判断,增加代码的健壮性。可是这样代码会显得十分臃肿。Java 语言的开发者们也在关注着这些问题。因此在 Java8 提供了新的 API:java.util.Optional 用来优雅的处理 null。接下来就请读者和我一起揭开 Optional 神秘的面纱吧!

PS:Optional 类提供的很多 API 结合 Lambda 表达式食用更佳,另外还有很多 API 和 Stream 流中同名 API 的思想基本一致。因此建议读者先行了解这两个知识点,可以在我的博客 Java8新特性 标签下学习

声明:本文首发于博客园,作者:后青春期的Keats;地址:https://www.cnblogs.com/keatsCoder/ 转载请注明,谢谢!

Optional 入门

Optional 类是一个封装 T 值的类,变量 T 存在时,Optional 只是对他做一个简单的封装,如果 T 不存在,缺失的值会被建模成一个空对象,由 Optional.empty() 返回。下面这张图可以形象的描述 Optional

现在我们尝试着重构之前关于 人 车 保险 的代码

public class Person {
private Optional<Car> car;
public Optional<Car> getCar() {
return car;
}
}
public class Car {
private Optional<Insurance> insurance;
public Optional<Insurance> getInsurance() {
return insurance;
}
}
public class Insurance {
private String name;
public String getName() {
return name;
}
}

注意:对于保险来说,我们从逻辑层面限定每个保险公司都有名称,如果没有,那一般是数据出了问题而非代码的问题,开发者应该着手去寻找为什么数据库存在名字为空的保险公司。而不是这里抛出 NPE,故而我们不用将 Insurance 的 name 字段使用 Optional 包裹

通过上面的代码,我们已经将对象由 Optional 所包裹了,那接下来我们该如何使用它呢?

创建 Optional 对象

创建一个空对象

Optional<Object> empty = Optional.empty();

Optional.empty(); 该方法返回一个空对象,

根据一个非空值创建

Optional<Car> car = Optional.of(c);

Optional.of(T t); 方法会返回一个 Optional 对象,但是需要注意,如果 of 方法参数是 null,该行会抛出 NPE。

允许空值创建

Optional car = Optional.ofNullable(c);

为了避免在创建 Optional 对象时,由于源对象为空而引发的 NPE,该类还提供了 ofNullable 方法,当参数为 null 时,返回 Optional.empty()。内部的 API 是这样的

public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}

map --- 从 Optional 中提取和转换值

public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent()) {
return empty();
} else {
return Optional.ofNullable(mapper.apply(value));
}
}

Optional 类提供 map 方法,接收一个函数式接口 Function 的实现类,如果调用者是空的,则返回 empty(),否则对 Optional 中的对象 value 调用 Function 实现类中的 apply() 方法,再包装成 Optional 返回。可以用下面的图直观的看到 map 执行的过程:

请注意,在 map 执行完 apply 方法拿到返回值之后,会主动将返回值再次包裹成 Optional 对象。因此我们如果按照下面的方式改造我们之前的方法,编译是无法通过的:

person.map(Person::getCar).map(Car::getInsurance).map(Insurance::getName);

我们来分析一下: person.map(Person::getCar) 改造后的 person 类中, getCar 方法返回 Optional 对象,而 map 又将 Optional 包装到 Optional 中,形成 Optional<Optional> emmm...套娃式包装。两层包装的 car 是无法通过 map 在调用 getInsurance 方法的。

幸运的是,和 Stream 一样,Optional 也提供了扁平化流的方法 flatMap()。且看源码

public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent()) {
return empty();
} else {
@SuppressWarnings("unchecked")
Optional<U> r = (Optional<U>) mapper.apply(value);
return Objects.requireNonNull(r);
}
}

flatMap() 比 map() 方法多了一个执行完后将嵌套 Optional 强转成 Optional 的操作,避免了流不能继续使用的尴尬处境。因此,我们可以将获取保险公司名称的方法改造成下面这样:

public String getCarInsuranceName(Optional<Person> person) {
return person.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown");
}

其中 orElse() 方法表示当最终 Optional 包裹的对象还是空时,返回的默认值

PS:由于 Optional 并没有实现序列化接口,因此如果你的项目中使用了某些要求序列化的框架,并且在某个类中使用 Optional 包裹了字段。可能会由序列化引发程序故障。

操作 Optional 中的变量

get()

通过 get() 方法获取变量,如果变量存在就直接得到该变量,否则抛出一个 throw new NoSuchElementException("No value present"); 异常。一般不建议使用该方法毕竟直接用 get() 方法了,还要整 Optional 这些花里胡哨的干啥呢

orElse()

在对象为 null 时提供一个默认值

orElseGet(Supplier<? extends T> other)

在对象为 null 通过调用 supplier 提供者接口的实现,返回一个值

orElseThrow()

在对象为 null 抛出一个可定制的异常信息,可以用来抛出项目中的自定义异常,以便全局异常捕获器抓取及响应数据

ifPresent(Consumer<? super T> action)

当对象不为 null 时,执行消费者操作。为 null 时啥也不干

更优雅的判断语句

我们常常调用某个对象的某个方法去判断其属性。为了安全操作。首先需要对该对象进行非空校验。例如要检查保险公司名称是否为 Keats,需要这么写

if(i != null && "Keats".equals(i.getName())){
System.out.println("yes");
}

现在我们可以这么写

Optional<Insurance> insurance = Optional.ofNullable(i);
insurance.filter(in -> "Keats".equals(in.getName())).ifPresent(in -> System.out.println("yes"));

先看 filter 的源码

public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent()) {
return this;
} else {
return predicate.test(value) ? this : empty();
}
}

首先第一步检查了谓词实现非空,第二步判断 Optional 中的对象如果为空则返回空 Optional,如果不为空执行谓词方法,条件成立则返回该对象。否则返回空 Optional。即仅当 Optional 中对象不为 null 且符合条件时,返回该对象之后通过 ifPresent() 方法执行接下来的逻辑。非常方便易懂

其他

Optional 还提供了一些基础类型对象对应的类,如 OptionalInt、OptionalLong 同 Stream 流一样,采用基本操作类型处理数据,避免了自动拆装箱带来的性能损失。但却牺牲了 map、flatMap、filter 方法。开发中需酌情使用

码字不易,如果你觉得读完以后有收获,不妨点个推荐让更多的人看到吧!

使用Optional,不再头疼NPE的更多相关文章

  1. JAVA8新特性Optional,非空判断

    Optional java 的 NPE(Null Pointer Exception)所谓的空指针异常搞的头昏脑涨, 有大佬说过 "防止 NPE,是程序员的基本修养." 但是修养归 ...

  2. 万恶的NPE差点让我半个月工资没了

    引言 最近看到<阿里巴巴Java开发手册>(公众号回复[开发手册]免费获取)第11条规范写到: 防止 NPE ,是程序员的基本修养 NPE(Null Pointer Exception)一 ...

  3. Web富媒体应用

    曾几何时,大家都在以flash开发的富媒体交互应用而感叹,一是叹它的丰富多彩的效果,一是叹它的局限.性能以及加载时长等问题. 如今,市场以及基本上没有flash什么事情了,而是H5的天下,可惜,移动应 ...

  4. 基于Lua的游戏服务端框架简介

    基于Lua的游戏服务端框架简介 [转]https://gameinstitute.qq.com/community/detail/106396 基于lua的游戏服务端框架简介 1. 引言 笔者目前在参 ...

  5. ClickHouse和他的朋友们(9)MySQL实时复制与实现

    本文转自我司大神 BohuTANG的博客 . 很多人看到标题还以为自己走错了夜场,其实没有. ClickHouse 可以挂载为 MySQL 的一个从库 ,先全量再增量的实时同步 MySQL 数据,这个 ...

  6. 【Java 8】巧用Optional之优雅规避NPE问题

    避之不及的 NullPointerException NPE : NullPointerException 空指针异常是最常见的Java异常之一,抛出NPE错误不是用户操作的错误,而是开发人员的错误, ...

  7. 【原创】JAVA8之妙用Optional解决NPE问题

    引言 在文章的开头,先说下NPE问题,NPE问题就是,我们在开发中经常碰到的NullPointerException.假设我们有两个类,他们的UML类图如下图所示 在这种情况下,有如下代码 user. ...

  8. 使用Optional摆脱NPE的折磨

    在目前的工作中,我对Java中的Stream和Lambda表达式都使用得很多,之前也写了两篇文章来总结对应的知识. 024:Java流实现Shell:cat 1.log | grep a | sort ...

  9. Java 8 Optional:优雅地避免 NPE

    本篇文章将详细介绍 Optional 类,以及如何用它消除代码中的 null 检查.在开始之前首先来看下什么是 NPE,以及在 Java 8 之前是如何处理 NPE 问题的. 空指针异常(NullPo ...

随机推荐

  1. U - Obtain a Permutation CodeForces - 1294E 思维

    题解: 注意每一列与每一列之间互不影响,所以贪心地求出没一列的最小操作值,然后累加起来. 怎么求没一列的最小值呢?维护一个数组same表示其中same[i]=j表示将该序列向上翻滚i次有j个元素归位, ...

  2. linux sysbench : CPU性能测试详解

    1.sysbench基础知识 sysbench的cpu测试是在指定时间内,循环进行素数计算 素数(也叫质数)就是从1开始的自然数中,无法被整除的数,比如2.3.5.7.11.13.17等.编程公式:对 ...

  3. 详解 Collection集合

    (请关注 本人"集合总集篇"博文--<详解 集合框架>) 首先,本人来讲解下 Collection集合的继承体系: Collection集合 的继承体系: Collec ...

  4. Python常用库-Psutil

    背景 介绍一个处理进程的实用工具,这个是一个第三方库.应用主要有类似ps.cd.top,还有查看硬盘.内存使用情况等. 推荐的理由主要有 2 个,第一个是跨平台的,不管是OSX.Centos.Wind ...

  5. 异常处理方式一(try-catch-finally)

    package com.yhqtv.demo01Exception; /* * 一.异常的处理,抓抛模型 * * 过程一:“抛”:程序在正常 执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异 ...

  6. C#线程学习笔记

    本笔记摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/18/Thread.html,记录一下学习,方便后面资料查找 一.线程的介绍 进程(Proce ...

  7. Python操作三大主流数据库

    Python 标准数据库接口为 Python DB-API,Python DB-API为开发人员提供了数据库应用编程接口. Python 数据库接口支持非常多的数据库,你可以选择适合你项目的数据库:  ...

  8. tp5--开启与关闭调试模式

    https://www.cnblogs.com/finalanddistance/p/8906000.html TP5 显示错误信息   在TP5中,我们运行的代码有错误无法执行时,只显示页面错误,而 ...

  9. Python3 字符串格式化(%操作符)

    格式符 格式符为真实值预留位置,并控制显示的格式.格式符可以包含有一个类型码,用以控制显示的类型,如下: %s    字符串 (采用str()的显示) %r    字符串 (采用repr()的显示) ...

  10. web前端开发中的各种居中

    居中是我们使用css来布局时常遇到的情况.使用css来进行居中时,有时一个属性就能搞定,有时则需要一定的技巧才能兼容到所有浏览器,本文就居中的一些常用方法做个简单的介绍. 注:本文所讲方法除了特别说明 ...