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