在目前的工作中,我对Java中的Stream和Lambda表达式都使用得很多,之前也写了两篇文章来总结对应的知识。

不过对于Optional这个特性,一直没有很好地使用起来,所以最近又开始阅读《Java 8实战》这本书,本文是针对其中第10章的一个学习总结。

背景

在Java中,如果你尝试对null做函数调用,就会引发NullPointerException(NPE),NPE是Java程序开发中的最典型的异常,对于Java开发者来说,无论你是初出茅庐的新人和还工作多年的老司机,NPE经常让他们翻车。为了避免NPE,他们会加很多if判断语句,使得代码的可读性变得很差。

从软件设计的角度来看,null本身是没有意义的语义,这是一种对缺失变量值的错误的建模。

从Java类型系统的角度看,null可以被赋值给任何类型的变量,并且不断被传递,知道最后谁也不知道它是从哪里引入的。

Optional的引入

Java设计者从Haskell和Scala中获取灵感,在Java 8中引入了一个新的类java.util.Optional<T>。如果一个接口返回Optional,可以表示一个人可能有车也可能没有车,这个比简单的返回Car要更明确,阅读代码的人不需要提前准备业务知识。

Optional的目的就在于此:通过类型系统让你的领域模型中隐藏的知识显式地体现在你的代码中。

Optional的使用

方法 描述
empty 返回一个空的Optional实例
filter 如果值存在并且满足提供的过滤条件,则返回包含该值的Optional对象;否则就返回一个空的Optional对象
map 如果值存在,就对该值执行提供的mapping函数调用
flatMap 如果值存在,就对该值执行提供的mapping函数调用,返回一个Optional类型的值,否则就返回一个空的Optional对象
ifPresent 如果值存在,就执行使用该值的方法调用,否则什么也不做
of 将指定值用Optional封装之后返回,如果该值为null,则抛出一个NPE
ofNullable 将指定值用Optional封装之后返回,如果该值为null,则返回一个空的Optional对象
orElse 如果有值则返回,否则返回一个默认值
orElseGet 如果有值则返回,否则返回一个由指定的Supplier接口生成的值(如果默认值的生成代价比较高的话,则适合使用orElseGet方法)
orElseThrow 如果有值则返回,否则返回一个由指定的Supplier接口抛出的异常
get 如果值存在,则返回该值,否则抛出一个NoSuchElementException异常
isPresent 如果值存在则返回true,否则返回false

上面这张表里列举了Optional的基础API,我这里列举了一些使用的tips:

  • 你可以用ofNullable将一个可能为null的对象封装为Optional对象,然后获取值的时候使用orElse方法提供默认值;可以使用empty方法创建一个空的Optional对象;of方法一般不用,不过如果你知道某个值不可能为null,则可以用Optional封装该值,这样它一旦为null就会抛出异常。
//empty方法的使用
Optional<Car> optCar = Optional.empty(); //of方法的使用
Optional<Car> optCar = Optional.of(car); //ofNullable方法的使用
Optional<Car> optCar = Optional.ofNullable(car);
  • 你可以使用map方法从Optional对象中它封装的值中的某个字段的值;
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);
  • 如果需要连续、层层递进的从某个对象链的末端获取字段的值,则不能全部使用map方法,需要先使用flatMap,最后再使用map方法;
//转换之前
public String getCarInsuranceName(Person person) {
return person.getCar().getInsurance().getName();
} //转换后
public String getCarInsuranceName(Optional<Person> person) {
return person.flatMap(Person::getCar)
.flatMap(Car::Insurance)
.map(Insurance::getName)
.orElse("Unknown");
}
  • Optional中的map、flatMap和filter方法,在概念是与Stream中对应的方法都很类似,区别就在于Optional中的元素至多有一个,算是Stream的一种特殊情况——一种特殊的集合。
  • 不要使用ifPresent和get方法,它们本质上和不适用Optional对象之前的模式相同,都是臃肿的if-then-else判断语句;
  • 由于Optional无法序列化,所以在领域模型中,无法将某个字段定义为Optional的,原因是:Optional的设计初衷仅仅是要支持能返回Optional对象的语法,如果我们希望在域模型中引入Optional,则可以用下面这种替代的方法:
public class Person {
private Car car;
public Optional<Car> getCarAsOptional() {
return Optional.ofNullable(car);
}
}
  • 不要使用基础类型的Optional对象,原因是:基础类型的Optional对象不支持map、flatMap和filter方法,而这些方法是Optional中非常强大的方法。

实战案例

案例1:使用工具类方法改良可能抛出异常的API

Java方法处理异常结果的方式有两种:返回null(或错误码);抛出异常,例如:Integer.parseInt(String)这个方法——如果无法解析到对应的整型,该方法就抛出一个NumberFormationException,这种情况下我们一般会使用try/catch语句处理异常情况。

一般我们建议将try/catch块单独提取到一个方法中,在这里使用Optional设计这个方法,代码如下。在开发中,可以尝试构建一个OptionalUtility工具类,将这些复杂的try/catch逻辑封装起来。

public static Optional<Integer> stringToInt(String a) {
try{
return Optional.of(Integer.parseInt(s));
} catch (NumberFormationException e) {
return Optional.empty();
}
}

案例2:综合案例

现在有个方法,是尝试从一个属性映射中获取某个关键词对应的值,例子代码如下:

   public static int readDuration(Properties properties, String name) {
String value = properties.getProperty(name);
if (value != null) {
try {
int i = Integer.parseInt(value);
if (i > 0) {
return i;
}
} catch (NumberFormatException e) { }
}
return 0;
}

使用Optional的写法后,代码如下所示:

    public static int readDurationWithOptional(Properties properties, String name) {
return Optional.ofNullable(properties.getProperty(name))
.flatMap(OptionalUtility::stringToInt)
.filter(integer -> integer > 0)
.orElse(0);
}

如果需要访问的属性值不存在,Properites.getProperty(String)方法的返回值就是一个null,使用noNullable工厂方法就可以将该值转换为Optional对象;接下来,可以使用flatMap将一个Optional转换为Optional对象;最后使用filter过滤掉负数,然后就可以使用orElse获取属性值,如果拿不到则返回默认值0。

总结

使用Optional的思路和Stream相同,都是链式思路,跟数据库查询似的,表达力很强,而且省去了哪些复杂的try/catch和if-then-else方法。在后面的开发中,可以使用Optional设计API,这样可以设计出更安全的接口和方法。


本号专注于后端技术、JVM问题排查和优化、Java面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。

使用Optional摆脱NPE的折磨的更多相关文章

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

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

  2. 通过SOFA看Java服务端如何实现运行时的模块化

    本文阅读时间大约7分钟. 今天我们谈谈SOFA模块化,首先看一段SOFA的介绍: SOFABoot是蚂蚁金服开源的基于Spring Boot的研发框架,它在Spring Boot的基础上,提供了诸如 ...

  3. 面试官问我,为什么老司机建议MySQL列属性尽量用 NOT NULL ?

    本文阅读时间大约6分钟. 其实写这篇文章,也是来自一个知识星球读者的提问,他在二面的过程中被问到了,由于他简历中写道有 MySQL 调优经验,但这个问题没有回答好,二面被刷了. 其实我们刚学习 C 语 ...

  4. idea万能快捷键,不可不知的17个实用技巧

    说明 IDEA里有一个万能快捷键(alt enter),功能非常强大,同一个快捷键,可以根据不同的语境提示你不同的操作,很多人可能还不了解这些功能,在处理代码的时候还手动处理,了解这些技巧之后,你编码 ...

  5. Java 8 Time Api 使用指南-珍藏限量版

    前面写过了Stream和Lambda,最近正想写Java 8的Time Api,小胖哥这个文章写得很好,就偷懒转载了. 1.概述 Java 8为Date和Time引入了新的API,以解决旧java.u ...

  6. 浅析Java 8新功能Optional

    初识 A container object which may or may not contain a non-null value. 笔者理解,Optional是一个容器类.将Object放到Op ...

  7. 序列化禁止使用Optional

    1: 概论 Optional 是Java8用来改变java引发NPE的解决办法,但是不是绝对的解决办法 2: 例子: 很多博文一上来就给力以下使用例子 @Data public class User ...

  8. Java 8新特性前瞻

    快端午小长假了,要上线的项目差不多完结了,终于有时间可以坐下来写篇博客了. 这是篇对我看到的java 8新特性的一些总结,也是自己学习过程的总结. 几乎可以说java 8是目前为止,自2004年jav ...

  9. Java8 Lambda 之 Collection Stream

    Lambda 之 Collection Stream Collection.stream() 测试实体类 class Demo { private Long id; private String na ...

随机推荐

  1. ES6/ES2015核心内容(转载)

    ES6其实就是ES2015,因为是2015年发布的,所以也叫ES2015.这个版本是JS的最新版本,很多浏览器还不支持,所有有了babel,专门把最新的JS转换一下,让大部分浏览器都支持的JS版本. ...

  2. Qt打开外部程序和文件夹需要注意的细节(注意QProcess的空格问题,以及打开本地文件时,需要QUrl::fromLocalFile才可以)

    下午写程序中遇到几个小细节,需要在这里记录一下. ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 QProcess *process = new QProcess(this ...

  3. java中==和equels的区别

    起初接触java的时候这个问题还是比较迷茫的,最近上班之余刷博客的时候看了一些大神写的文章,自己也来总结一下,直接贴代码: package string; public class demo1 { p ...

  4. 浅谈Android高通(Qualcomm)和联发科(MTK)平台

    一款CPU好不好是要从多个方面考虑的,并不是说简简单单看一个主频.几个核心数就完了,更重要的是它的综合实力到底有多强,这里面当然也会牵扯到价格问题,性能相似当然是便宜的获胜,这是毋庸置疑的. 事实上, ...

  5. 写给非专业人士看的 *** 简介(同时也解释了GFW )

    写给非专业人士看的 *** 简介 这个文章来源于一个朋友在***的过程中,搞不清楚 *** 的配置问题,在这里我想按照我对 *** 的理解简单梳理一下,以便一些非专业人士也能了解 long long ...

  6. Linux 下 Redis 服务 Shell启动脚本

    # chkconfig: 2345 10 90 # description: Start and Stop redis  PATH=/usr/local/bin:/sbin:/usr/bin:/bin ...

  7. 大神为你分析 Go、Java、C 等主流编程语言(Go可以替代Java,而且最小化程序员的工作量,学习比较容易)

    本文主要分析 C.C++98.C++11.Java 与 Go,主要论述语言的关键能力.在论述的过程中会结合华为各语言编程专家和华为电信软件内部的骨干开发人员的交流,摒弃语言偏好或者语言教派之争,尽量以 ...

  8. test判断条件

      一:Shell test 命令        1.数值测试       参数 说明 -eq 等于则为真 -ne 不等于则为真 -gt 大于则为真 -ge 大于等于则为真 -lt 小于则为真 -le ...

  9. 为mysql数据备份建立最小权限的用户

    mysqldump 备份所需要的最小权限说明: 1.对于table,mysqldump 最少要有select权限 2.如果要产生一份一致的备份,mysqldump 要有lock tables权限 3. ...

  10. linux Apache设置https访问以及加载mod_ssl.so模块以及问题解决

    开始之前的话: 1.配置好服务器防火墙的443端口规则: 2.购买好证书文件,我是沃通证书,准备好证书,这里不演示证书的购买和安装. 3.根据服务器类型下载文件,apache一共有4个文件 这里提供沃 ...