提到NullPointerException(简称NPE)异常,相信每个Java开发人员都不陌生,从接触编程的第1天起,它就和我们如影随形,最近处理的线上bug中,有不少都是对象没判空导致的NullPointerException异常。
引起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()); } }
项目中类似的判空代码应该有很多,大家可以自行看下自己项目的代码。
为了避免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方法的使用:
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方法的使用方法如下所示:
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); }
输出结果:
对于程序员来说,一不注意就会出现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异常才重要。
理解、学习与使用 Java 中的 Optional
到此这篇关于Java中Optional使用的文章就介绍到这了,更多相关Java Optional使用内容请搜索小牛知识库以前的文章或继续浏览下面的相关文章希望大家以后多多支持小牛知识库!
我试图重构旧代码以使用流,我的第一个方法是: 这不是编译,因为new Image()和Images.write()抛出IOExceptions。 用UncheckedIOException包装这些异常不会起作用,因为如果其中一个图像失败,我不想停止处理其他图像。 所以我结束了写2个私人方法: createImage()返回一个可选值,因为这看起来很明智。然而在此之后,我的代码变得非常丑陋: 有没有
问题内容: 我已经在许多网站上阅读了Optional应该仅用作返回类型,而不能在方法参数中使用。我正在努力寻找一个合理的理由。例如,我有一段逻辑,其中包含2个可选参数。因此,我认为这样写我的方法签名(解决方案1)会很有意义: 许多指定为的网页不应用作方法参数。考虑到这一点,我可以使用以下方法签名并添加清晰的注释以指定参数可以为null,希望将来的维护者将读取,因此始终在使用参数之前进行检查(解决方
22.4 SRPM 的使用 : rpmbuild (Optional) 谈完了 RPM 类型的软件之后,再来我们谈一谈包含了 Source code 的 SRPM 该如何使用呢?假如今天我们由网络上面下载了一个 SRPM 的文件,该如何安装他?又,如果我想要修改这个 SRPM 里面源代码的相关设置值,又该如何订正与重新编译呢? 此外,最需要注意的是,新版的 rpm 已经将 RPM 与 SRPM 的
问题内容: 这是Java 8中Optional类型的有效(预期)用法吗? 问题答案: 我会再摇摆一下。 这是有效用法吗?是的,从狭义上讲,它可以编译并产生您期望的结果。 这是预期用途吗?否。现在,有时候发现事情超出了最初的用途,如果行得通,那就太好了。但是对于,我们发现通常情况下效果并不理想。 在我们的JavaOne 2015演讲“ Java 8 Lambda和Streams的API设计”中 ,我
问题内容: 新的Java 8流框架和新朋友创建了一些非常简洁的Java代码,但是我遇到了一个看似简单的情况,很难做到简洁。 考虑一个和方法。我想将映射到Optional s并获得第一个Other。显而易见的解决方案是使用 ,但是 要求你返回一个流,并且 没有 方法(或者它是 或提供一种将其转换为或将其视为的方法 )。 我能想到的最好的方法是: 但这对于一个很常见的案例来说似乎太漫长了。有人有更好的
问题内容: 现在已经使用Java 8超过6个月左右,我对新的API更改感到非常满意。我仍然不自信的一个领域是什么时候使用。我似乎在想要在可能存在的任何地方都使用它而根本不在任何地方使用之间切换。 我似乎可以在许多情况下使用它,但我不确定它是否会带来好处(可读性/空安全性)或仅引起额外的开销。 因此,我有一些示例,我会对社区关于是否有益的想法感兴趣。 1-作为方法可以返回时的公共方法返回类型null