Java中Optional的使用指南

提到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使用内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 使用Java8中Optional机制的正确姿势

    前言 Java8带来的函数式编程特性对于习惯命令式编程的程序员来说还是有一定的障碍的,我们只有深入了解这些机制的方方面面才能运用自如.Null的处理在JAVA编程中是出了try catch之外的另一个头疼的问题,需要大量的非空判断模板代码,程序逻辑嵌套层次太深.尤其是对集合的使用,需要层层判空. 首先来看下Optional类的结构图: 而如果我们对它不稍假探索, 只是轻描淡写的认为它可以优雅的解决 NullPointException 的问题, 于是代码就开始这么写了 Optional<User

  • Java8中Optional类型和Kotlin中可空类型的使用对比

    本文主要给大家介绍了关于Java8中Optional类型和Kotlin中可空类型使用的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: 在 Java 8中,我们可以使用 Optional 类型来表达可空的类型. package com.easy.kotlin; import java.util.Optional; import static java.lang.System.out; /** * Optional.ofNullable - 允许传递为 null 参数 *

  • Java如何使用Optional与Stream取代if判空逻辑(JDK8以上)

    通过本文你可以用非常简短的代码替代业务逻辑中的判null校验,并且很容易的在出现空指针的时候进行打日志或其他操作. 注:如果对Java8新特性中的lambda表达式与Stream不熟悉的可以去补一下基础,了解概念. 首先下面代码中的List放入了很多Person对象,其中有的对象是null的,如果不加校验调用Person的getXXX()方法肯定会报空指针错误,一般我们采取的方案就是加上if判断: public class DemoUtils { public static void main(

  • 如何使用Java中的Optional

    NullPointerException是非常常见的异常.由于它,程序往往需要大量使用if-else代码块来处理空值,这使得代码看起来不简洁 优雅 ,且不方便自己和他人阅读.本文介绍如何用Optional类来处理null值问题. Optional类 先来看一段代码: String isocode = user.getAddress().getCountry().getIsocode().toUpperCase(); 这段代码在任何一个方法调用时,都有可能抛出NullPointerExceptio

  • java8中forkjoin和optional框架使用

    并行流与串行流 并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流. java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作.Stream API 可以声明性地通过 parallel()与 sequential()在并行流与顺序流之间进行切换. 了解 Fork/Join 框架 Fork/Join 框架:就是在必要的情况下,将一个大任务,进形拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运行的结果进行join汇总. Fork/Join 框架

  • JAVA开发常用类库UUID、Optional、ThreadLocal、TimerTask、Base64使用方法与实例详解

    1.UUID类库 UUID 根据时间戳实现自动无重复字符串定义 // 获取UUID public static UUID randomUUID() // 根据字符串获取UUID public static UUID fromString(String name) 应用:对文件进行自动命名处理 import java.util.UUID; class Demo { public static void main(String[] args) { System.out.println(UUID.ra

  • Java中Optional的使用指南

    提到NullPointerException(简称NPE)异常,相信每个Java开发人员都不陌生,从接触编程的第1天起,它就和我们如影随形,最近处理的线上bug中,有不少都是对象没判空导致的NullPointerException异常. 1. 简单回顾 引起NullPointerException异常的地方有很多,比如调用String的trim()方法,比如对BigDecimal进行计算时,比如将包装类型转化为基本类型时,这里简单回顾下. 假设有个导入模版定义如下: package com.zw

  • java中Optional的使用详细解析

    Optional的使用详解 1.Optional介绍 Optional 类是一个可以为null的容器对象.如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象. Optional 是个容器:它可以保存类型T的值,或者仅仅保存null.Optional提供很多有用的方法,这样我们就不用显式进行空值检测. Optional 类的引入很好的解决空指针异常. 2.构建Optional 构建一个Optional对象:方法有:empty( ).of( ).ofNullable

  • 详解Java中Optional类的使用方法

    目录 一.Optional类的来源 二.Optional类是什么 三.Optional类用法 四.代码示例 1.创建Optional类 2.判断Optional容器中是否包含对象 3.获取Optional容器的对象 4.过滤 5.映射 五.什么场景用Optional 1.场景一 2.场景二 3.场景三 4.场景四 一.Optional类的来源 到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因.以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optiona

  • Java中Optional类及orElse方法详解

    目录 引言 Java 中的 Optional 类 ofNullable() 方法 orElse() 方法 案例 orElseGet() 方法 案例 orElse() 与 orElseGet() 之间的区别 引言 为了让我更快的熟悉代码,前段时间组长交代了一个小任务,大致就是让我整理一下某个模块中涉及的 sql,也是方便我有目的的看代码,也是以后方便他们查问题(因为这个模块,涉及的判断很多,所以之前如果 sql 出错了,查问题比较繁琐). 昨天算是基本完成了,然后今天组长就让给我看一个该模块的缺陷

  • java中optional的一些常用方法总结

    目录 前言 1. 创建Optional对象 2. 获取Optional对象的值 3. 判断Optional对象是否包含非空值 4. 获取Optional对象中的值或默认值 5. 获取Optional对象中的值或抛出异常 6. 转换Optional对象中的值 7. 过滤Optional对象中的值 Java 9 增强 补充:Optional争议点 总结 前言 Java中的Optional是一个容器对象,它可以包含一个非空值,也可以为空.它的主要作用是在编写代码时避免空指针异常. java 8 中Op

  • 详解JAVA中的OPTIONAL

    一.概述 本质上,这是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为空. Optional 是 Java 实现函数式编程的强劲一步,并且帮助在范式中实现.但是 Optional 的意义显然不止于此. 我们从一个简单的用例开始.在 Java 8 之前,任何访问对象方法或属性的调用都可能导致NullPointerException: String isocode = user.getAddress().getCountry().getIsocode().toUpper

  • 学会在Java中使用Optional功能

    目录 前言 Nullity Optional Class 客户责任 null Optional Objects 重要方法 创建方法 of ofNullable empty 实例方法 isPresent&isEmpty get orElse系列 orElseThrow系列 ifPresent系列 map flatMap filter 何时使用 返回值 字段 参数 替代方案 null 空对象 例外情况 结论 前言 尽管存在争议,但Optiont极大地改进了Java应用程序的设计.在本文中,我们将了解

  • Java中的Optional处理方法

    目录 java.util.Optional 使用Optional构建对象 获取Optional中的对象 Optional 中map和flatmap的差别 在我们日常的开发中,我们经常会遇到 NullPointerException.如何才能优雅的处理NPE?这里告诉大家一个较为流行的方法 java.util.Optional 使用Optional来修饰对象,表示这个对象可能为null.在使用时,就要加以注意,必须要考虑该值为null的场景. 使用Optional构建对象 // 创建一个空的car

  • Java中的static的使用指南

    一.Java中的static使用之静态变量 1.Java 中被static修饰的成员称为静态成员或类成员.它属于整个类所有,而不是某个对象所有,即被类的所有对象所共享.且优先于对象存在.静态成员可以使用类名直接访问,也可以使用对象名进行访问.使用 static 可以修饰变量.方法和代码块. 2.public 修饰符表示公开的.公有的,静态变量使用static修饰 3.静态方法中可以直接调用同类中的静态成员,但不能直接调用非静态成员. public class HellWorld{ String

随机推荐