Java中Optional的使用指南

 更新时间:2021年1月31日 15:00  点击:2163

提到NullPointerException(简称NPE)异常,相信每个Java开发人员都不陌生,从接触编程的第1天起,它就和我们如影随形,最近处理的线上bug中,有不少都是对象没判空导致的NullPointerException异常。

1. 简单回顾

引起NullPointerException异常的地方有很多,比如调用String的trim()方法,比如对BigDecimal进行计算时,比如将包装类型转化为基本类型时,这里简单回顾下。

假设有个导入模版定义如下:

package com.zwwhnly.springbootaction.model;

import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * 导入模版
 */
@Data
@AllArgsConstructor
public class ImportTemplate {
 /**
 * 模版id
 */
 private int templateId;

 /**
 * 模版名称
 */
 private String templateName;

 /**
 * 模版下载url
 */
 private String url;

 /**
 * 备注
 */
 private String remark;
}

然后看下如下代码:

public static void main(String[] args) {
 ImportTemplate importTemplate = getImportTemplateById(1);
 System.out.println(importTemplate.getUrl());
}

public static ImportTemplate getImportTemplateById(int id) {
 return new ImportTemplate(1, "销售订单-普通商品导入模版", "o_w-140e3c1f41c94f238196539558e25bf7", null);
}

正常情况下,这段代码肯定是没有问题的,但当getImportTemplateById方法返回null时,这段代码就会抛出NullPointerException异常,如下所示:

public static ImportTemplate getImportTemplateById(int id) {
 return null;
}

为了程序能正常运行,就要判断importTemplate是否为null,所以代码就修改为了:

public static void main(String[] args) {
 ImportTemplate importTemplate = getImportTemplateById(1);
 if (importTemplate != null) {
 System.out.println(importTemplate.getUrl());
 }
}

项目中类似的判空代码应该有很多,大家可以自行看下自己项目的代码。

2. 使用Optional

为了避免NullPointerException异常,JDK1.8新增了Optional类来处理空指针异常,该类位于java.util包下,提供了一系列方法,

并且可以配合Lambda表达式一起使用,使代码看起来更加清晰,接下来我们看下它的使用方法。

2.1 创建实例

创建Optional实例有以下3种方式,分别为:

调用empty方法

Optional<ImportTemplate> optionalImportTemplate = Optional.empty();

调用of方法

ImportTemplate importTemplate = new ImportTemplate(1, "销售订单-普通商品导入模版",
 "o_w-140e3c1f41c94f238196539558e25bf7", null);
Optional<ImportTemplate> optionalImportTemplate = Optional.of(importTemplate);

调用ofNullable方法(推荐)

ImportTemplate importTemplate = new ImportTemplate(1, "销售订单-普通商品导入模版",
  "o_w-140e3c1f41c94f238196539558e25bf7", null);
Optional<ImportTemplate> optionalImportTemplate = Optional.ofNullable(importTemplate);

值得注意的是,当参数为null时,调用of方法会抛NullPointerException异常,但调用ofNullable方法不会(更符合使用场景),因此推荐使用ofNullable方法:

ImportTemplate importTemplate = null;
Optional<ImportTemplate> optionalImportTemplate = Optional.of(importTemplate);

2.2 判断是否有值

可以调用isPresent方法来判断对象是否有值(不为null),使用方法如下所示:

ImportTemplate importTemplate = null;
Optional<ImportTemplate> optionalImportTemplate = Optional.ofNullable(importTemplate);

System.out.println(optionalImportTemplate.isPresent());

以上代码的输出结果为:

ImportTemplate importTemplate = new ImportTemplate(1, "销售订单-普通商品导入模版",
  "o_w-140e3c1f41c94f238196539558e25bf7", null);
Optional<ImportTemplate> optionalImportTemplate = Optional.ofNullable(importTemplate);

System.out.println(optionalImportTemplate.isPresent());

以上代码的输出结果为:

看下isPresent的源码,逻辑非常简单,就是判断了我们传入的对象是否有值,即不为null:

/**
 * Return {@code true} if there is a value present, otherwise {@code false}.
 *
 * @return {@code true} if there is a value present, otherwise {@code false}
 */
public boolean isPresent() {
 return value != null;
}

2.3 获取值

可以调用get方法来获取对象的有值,使用方法如下所示:

ImportTemplate importTemplate = new ImportTemplate(1, "销售订单-普通商品导入模版",
  "o_w-140e3c1f41c94f238196539558e25bf7", null);
Optional<ImportTemplate> optionalImportTemplate = Optional.ofNullable(importTemplate);
System.out.println(optionalImportTemplate.get());

以上代码的输出结果为:

值得注意的是,当我们传入的对象为null时,调用get方法会抛出java.util.NoSuchElementException异常,而不是返回null。

ImportTemplate importTemplate = null;
Optional<ImportTemplate> optionalImportTemplate = Optional.ofNullable(importTemplate);
System.out.println(optionalImportTemplate.get());

以上代码的输出结果为:

看下get方法的源码,就可以知道原因:

public T get() {
 if (value == null) {
  throw new NoSuchElementException("No value present");
 }
 return value;
}

2.4 先用isPresent,再用get(不推荐)

然后我们回顾下文初的代码:

ImportTemplate importTemplate = getImportTemplateById(1);
if (importTemplate != null) {
 System.out.println(importTemplate.getUrl());
}

可能很多同学会把代码优化为下面这样的写法:

Optional<ImportTemplate> optionalImportTemplate = Optional.ofNullable(getImportTemplateById(1));
if (optionalImportTemplate.isPresent()) {
 System.out.println(optionalImportTemplate.get().getUrl());
}

不推荐这么使用,因为判断的地方没减少,而且还不如原来看起来清晰。

2.5 ifPresent(推荐)

那该怎么优化呢?答案就是使用ifPresent方法,该方法接收一个Consumer类型的参数,当值不为null时,就执行,当值为null时,就不执行,源码如下所示:

public void ifPresent(Consumer<? super T> consumer) {
 if (value != null)
  consumer.accept(value);
}

优化之后的代码如下所示:

Optional<ImportTemplate> optionalImportTemplate = Optional.ofNullable(getImportTemplateById(1));
optionalImportTemplate.ifPresent(importTemplate -> System.out.println(importTemplate.getUrl()));

当然,也可以写更多的逻辑:

Optional<ImportTemplate> optionalImportTemplate = Optional.ofNullable(getImportTemplateById(1));
optionalImportTemplate.ifPresent(importTemplate -> {
 System.out.println(importTemplate.getTemplateId());
 System.out.println(importTemplate.getTemplateName());
 System.out.println(importTemplate.getUrl());
 System.out.println(importTemplate.getRemark());
});

2.6 自定义默认值

Optional类提供了以下2个方法来自定义默认值,用于当对象为null时,返回自定义的对象:

  • orElse
  • orElseGet

先来看下orElse方法的使用:

public static void main(String[] args) {
 ImportTemplate importTemplate = null;
 ImportTemplate firstImportTemplate = Optional.ofNullable(importTemplate)
   .orElse(getDefaultTemplate());
 System.out.println(firstImportTemplate);

 importTemplate = new ImportTemplate(2, "销售订单-不定规格商品导入模版", "o_w-a7109db89f8d4508b4c6202889a1a2c1", null);

 ImportTemplate secondImportTemplate = Optional.ofNullable(importTemplate)
   .orElse(getDefaultTemplate());
 System.out.println(secondImportTemplate);
}

public static ImportTemplate getDefaultTemplate() {
 System.out.println("getDefaultTemplate");
 return new ImportTemplate(1, "销售订单-普通商品导入模版", "o_w-140e3c1f41c94f238196539558e25bf7", null);
}

输出结果:

再来看下orElseGet方法的使用:

public static void main(String[] args) {
 ImportTemplate importTemplate = null;
 ImportTemplate firstImportTemplate = Optional.ofNullable(importTemplate)
   .orElseGet(() -> getDefaultTemplate());
 System.out.println(firstImportTemplate);

 importTemplate = new ImportTemplate(2, "销售订单-不定规格商品导入模版", "o_w-a7109db89f8d4508b4c6202889a1a2c1", null);

 ImportTemplate secondImportTemplate = Optional.ofNullable(importTemplate)
   .orElseGet(() -> getDefaultTemplate());
 System.out.println(secondImportTemplate);
}

public static ImportTemplate getDefaultTemplate() {
 System.out.println("getDefaultTemplate");
 return new ImportTemplate(1, "销售订单-普通商品导入模版", "o_w-140e3c1f41c94f238196539558e25bf7", null);
}

输出结果:

从输出结果看,2个方法好像差不多,第1次调用都返回了默认模版,第2次调用都返回了传入的模版,但其实仔细观察,你会发现当使用

orElse方法时,getDefaultTemplate方法执行了2次,但调用orElseGet方法时,getDefaultTemplate方法只执行了2次(只在第1次传入模版为null时执行了)。

为什么会这样呢?带着这个疑问,我们看下这2个方法的源码,其中orElse方法的源码如下所示:

public T orElse(T other) {
 return value != null ? value : other;
}

可以看到,参数other是个对象,这个参数肯定是要传的,但只有value为空时,才会用到(返回)这个对象。

orElseGet方法的源码如下所示:

public T orElseGet(Supplier<? extends T> other) {
 return value != null ? value : other.get();
}

可以看到,参数other并不是直接传入对象,如果value为null,才会执行传入的参数获取对象,如果不为null,直接返回value。

2.7 自定义异常

Optional类提供了orElseThrow方法,用于当传入的对象为null时,抛出自定义的异常,使用方法如下所示:

public static void main(String[] args) {
 ImportTemplate importTemplate = new ImportTemplate(2, "销售订单-不定规格商品导入模版", "o_w-a7109db89f8d4508b4c6202889a1a2c1", null);
 ImportTemplate firstImportTemplate = Optional.ofNullable(importTemplate)
   .orElseThrow(() -> new IndexOutOfBoundsException());
 System.out.println(firstImportTemplate);

 importTemplate = null;

 ImportTemplate secondImportTemplate = Optional.ofNullable(importTemplate)
   .orElseThrow(() -> new IndexOutOfBoundsException());
 System.out.println(secondImportTemplate);
}

输出结果:

2.8 过滤数据

Optional类提供了filter方法来过滤数据,该方法接收一个Predicate参数,返回匹配条件的数据,如果不匹配条件,返回一个空的Optional,使用方法如下所示:

public static void main(String[] args) {
 ImportTemplate importTemplate = getImportTemplateById(1);
 Optional<ImportTemplate> filterById = Optional.ofNullable(importTemplate)
   .filter(f -> f.getTemplateId() == 1);
 System.out.println(filterById.isPresent());

 Optional<ImportTemplate> filterByName = Optional.ofNullable(importTemplate)
   .filter(f -> f.getTemplateName().contains("发货单"));
 System.out.println(filterByName.isPresent());
}

public static ImportTemplate getImportTemplateById(int id) {
 return new ImportTemplate(1, "销售订单-普通商品导入模版", "o_w-140e3c1f41c94f238196539558e25bf7", null);
}

输出结果:

2.9 转换值

Optional类提供了以下2个方法来转换值:

  • map
  • flatMap

map方法的使用方法如下所示:

public static void main(String[] args) {
 ImportTemplate importTemplate = getImportTemplateById(1);
 Optional<String> optionalUrl = Optional.ofNullable(importTemplate)
   .map(f -> "url:" + f.getUrl());
 System.out.println(optionalUrl.isPresent());
 System.out.println(optionalUrl.get());
}

public static ImportTemplate getImportTemplateById(int id) {
 return new ImportTemplate(1, "销售订单-普通商品导入模版", "o_w-140e3c1f41c94f238196539558e25bf7", null);
}

输出结果:

flatMap方法和map方法类似,不过它支持传入Optional,使用方法如下所示:

public static void main(String[] args) {
 ImportTemplate importTemplate = getImportTemplateById(1);
 Optional<String> optionalUrl = Optional.ofNullable(importTemplate)
   .flatMap(f -> Optional.ofNullable(f.getUrl()));
 System.out.println(optionalUrl.isPresent());
 System.out.println(optionalUrl.get());
}

public static ImportTemplate getImportTemplateById(int id) {
 return new ImportTemplate(1, "销售订单-普通商品导入模版", "o_w-140e3c1f41c94f238196539558e25bf7", null);
}

输出结果:

3. 总结

对于程序员来说,一不注意就会出现NullPointerException异常,避免它的方式也很简单,比如使用前判断不能为空:

public static void main(String[] args) {
 ImportTemplate importTemplate = getImportTemplateById(1);
 if (importTemplate != null) {
  System.out.println(importTemplate.getUrl());
 }
}

比如为空时,直接返回(或者返回默认值):

public static void main(String[] args) {
 ImportTemplate importTemplate = getImportTemplateById(1);
 if (importTemplate == null) {
  return;
 }
 System.out.println(importTemplate.getUrl());
}

比如,使用本文中的Optional。

使用哪种方式不重要,尽可能地避免NullPointerException异常才重要。

4. 参考

理解、学习与使用 Java 中的 Optional

总结

到此这篇关于Java中Optional使用的文章就介绍到这了,更多相关Java Optional使用内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

[!--infotagslink--]

相关文章

  • Java实现经典游戏复杂迷宫

    这篇文章主要介绍了如何利用java语言实现经典《复杂迷宫》游戏,文中采用了swing技术进行了界面化处理,感兴趣的小伙伴可以动手试一试...2022-02-01
  • java 运行报错has been compiled by a more recent version of the Java Runtime

    java 运行报错has been compiled by a more recent version of the Java Runtime (class file version 54.0)...2021-04-01
  • 在java中获取List集合中最大的日期时间操作

    这篇文章主要介绍了在java中获取List集合中最大的日期时间操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
  • 图解PHP使用Zend Guard 6.0加密方法教程

    有时为了网站安全和版权问题,会对自己写的php源码进行加密,在php加密技术上最常用的是zend公司的zend guard 加密软件,现在我们来图文讲解一下。 下面就简单说说如何...2016-11-25
  • ps怎么使用HSL面板

    ps软件是现在很多人都会使用到的,HSL面板在ps软件中又有着非常独特的作用。这次文章就给大家介绍下ps怎么使用HSL面板,还不知道使用方法的下面一起来看看。 &#8195;...2017-07-06
  • 教你怎么用Java获取国家法定节假日

    这篇文章主要介绍了教你怎么用Java获取国家法定节假日,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下...2021-04-23
  • Java如何发起http请求的实现(GET/POST)

    这篇文章主要介绍了Java如何发起http请求的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-31
  • 浅谈Java与C#的一些细微差别

    说起C#和Java这两门语言(语法,数据类型 等),个人以为,大概有90%以上的相似,甚至可以认为几乎一样。但是在工作中,我也发现了一些细微的差别...2020-06-25
  • Plesk控制面板新手使用手册总结

    许多的朋友对于Plesk控制面板应用不是非常的了解特别是英文版的Plesk控制面板,在这里小编整理了一些关于Plesk控制面板常用的使用方案整理,具体如下。 本文基于Linu...2016-10-10
  • 使用insertAfter()方法在现有元素后添加一个新元素

    复制代码 代码如下: //在现有元素后添加一个新元素 function insertAfter(newElement, targetElement){ var parent = targetElement.parentNode; if (parent.lastChild == targetElement){ parent.appendChild(newEl...2014-05-31
  • 解决Java处理HTTP请求超时的问题

    这篇文章主要介绍了解决Java处理HTTP请求超时的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-29
  • 使用GruntJS构建Web程序之构建篇

    大概有如下步骤 新建项目Bejs 新建文件package.json 新建文件Gruntfile.js 命令行执行grunt任务 一、新建项目Bejs源码放在src下,该目录有两个js文件,selector.js和ajax.js。编译后代码放在dest,这个grunt会...2014-06-07
  • 使用percona-toolkit操作MySQL的实用命令小结

    1.pt-archiver 功能介绍: 将mysql数据库中表的记录归档到另外一个表或者文件 用法介绍: pt-archiver [OPTION...] --source DSN --where WHERE 这个工具只是归档旧的数据,不会对线上数据的OLTP查询造成太大影响,你可以将...2015-11-24
  • 如何使用php脚本给html中引用的js和css路径打上版本号

    在搜索引擎中搜索关键字.htaccess 缓存,你可以搜索到很多关于设置网站文件缓存的教程,通过设置可以将css、js等不太经常更新的文件缓存在浏览器端,这样访客每次访问你的网站的时候,浏览器就可以从浏览器的缓存中获取css、...2015-11-24
  • jQuery 1.9使用$.support替代$.browser的使用方法

    jQuery 从 1.9 版开始,移除了 $.browser 和 $.browser.version , 取而代之的是 $.support 。 在更新的 2.0 版本中,将不再支持 IE 6/7/8。 以后,如果用户需要支持 IE 6/7/8,只能使用 jQuery 1.9。 如果要全面支持 IE,并混合...2014-05-31
  • java 判断两个时间段是否重叠的案例

    这篇文章主要介绍了java 判断两个时间段是否重叠的案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
  • 安装和使用percona-toolkit来辅助操作MySQL的基本教程

    一、percona-toolkit简介 percona-toolkit是一组高级命令行工具的集合,用来执行各种通过手工执行非常复杂和麻烦的mysql和系统任务,这些任务包括: 检查master和slave数据的一致性 有效地对记录进行归档 查找重复的索...2015-11-24
  • C#注释的一些使用方法浅谈

    C#注释的一些使用方法浅谈,需要的朋友可以参考一下...2020-06-25
  • 超简洁java实现双色球若干注随机号码生成(实例代码)

    这篇文章主要介绍了超简洁java实现双色球若干注随机号码生成(实例代码),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-04-02
  • MySQL日志分析软件mysqlsla的安装和使用教程

    一、下载 mysqlsla [root@localhost tmp]# wget http://hackmysql.com/scripts/mysqlsla-2.03.tar.gz--19:45:45-- http://hackmysql.com/scripts/mysqlsla-2.03.tar.gzResolving hackmysql.com... 64.13.232.157Conn...2015-11-24