深入挖掘为什么要使用Objects.requireNonNull方法

引入

在一次学习过程中,通过ImageIO.read方法访问本地图片的时候,IDEA给出了Warning并提出了改善建议:

//源码为:
bullet=ImageIO.read(ResourceMgr.class.getClassLoader().getResource 
          AsStream("image/tankmissile.gif"));
//改善建议为:在此基础上,增加Objects.requireNonNull方法
bullet=ImageIO.read(Objects.requireNonNull(ResourceMgr.class.getClassLoader().getResource			 
          AsStream("image/tankmissile.gif")));

 即在一个需求input参数的时候,将输入的参数以Objects.requireNonNull的方法“包装了一层”。

在实际使用过程中,我发现不论这里是否使用Objects.requireNonNull方法进行包装,在编译的时候均会报出相应的空指针异常。既然如此,那么这里为什么编译器会建议加上该方法呢?

源码

好吧,严谨的学习态度不愿意让我放过这一点小的问题,所以我们首先查看一下源码:

//方法一:
    public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }

//方法二:
    public static <T> T requireNonNull(T obj, String message) {
        if (obj == null)
            throw new NullPointerException(message);
        return obj;
    }

可以看到,在源码中,该方法提供了两套不同的输入参数,不过其处理逻辑是相同的:

给requireNonNull的方法的输入参数为空的时候,抛出NullPointerException的异常。

区别仅是,方法二还可以在调用的时候输入message,以在抛出异常的时候可以用字符串进行标识。

通过看源码,我们初步了解到了使用Objects.requireNonNull方法的好处:

  1. 可以非常直观地在查看源码的同时,了解到这里可能会出现空指针异常的现象。
  2. 当存在多处使用requireNonNull方法的时候,可以用不同的错误信息让我们更直观地了解问题所在之处,或者问题的原因。

可是真的只有这样吗?让我们进行一次对比,将源码中的.gif内容删除,我们可以获得两份不同的错误日志:

//省略一部分......
Caused by: java.lang.IllegalArgumentException: input == null!
    at java.desktop/javax.imageio.ImageIO.read(ImageIO.java:1356)
    at com.wust.tank.ResourceMgr.<clinit>(ResourceMgr.java:25)

//调用Objects.requireNonNull方法
//省略一部分......
Caused by: java.lang.NullPointerException
    at java.base/java.util.Objects.requireNonNull(Objects.java:221)
    at com.wust.tank.ResourceMgr.<clinit>(ResourceMgr.java:25)

思考

这可就尴尬了,在加上方法后,虽然抛出的异常机制不同(一个是ImageIO.read抛出,一个是requireNonNull抛出),但我们均能索引到对应代码出现错误的地方(两个错误日志的第三行)。

以这般看来,使用Objects.requireNonNull方法并没有带来更加直观的好处(除了可以自定义异常日志语句,通过调用方法二),那么为什么我们还要加上这一部分,来增加代码量呢?

抱着这么一个思考,我们继续我们的探究。

探索

思考错误日志时,我发现了一个很重要的内容,即:

ImageIO.read() 抛出的,是其中的inputStream对象为空的异常

而当我们创建inputStream对象的时候,通常不会出现空指针异常的错误

由此启发,我们修改我们的源码,我们尝试“先创建对象”,“后加入引用”的方式,以查看Objects.requireNonNull的真正效果:

InputStream inputStream = ResourceMgr.class.getClassLoader().getResourceAsStream(
                    "image/p1tankD");
tankD = ImageIO.read(inputStream);


//修改后的requireNonNull
InputStream inputStream = Objects.requireNonNull(ResourceMgr.class.getClassLoader().
                    getResourceAsStream("image/p1tankD"));
tankD = ImageIO.read(inputStream);

在这里,我们才发现了Objects.requireNonNull的真正用法:

  1. 在创建对象的时候,就可以检查对象是否为Null(而非在其他方法中调用对象的时候)
  2. requireNonNull使用的对象会被视为"guaranteed not null"的状态,即在调用该方法后,会确保该对象为非空指针(为空则抛出,不会进入后续),因此在后续程序中,会把该对象视为“非空指针”对象,不会进行相应的Warning提示(如ImageIO.read()提示的可能为空指针等)

结论

这里,其实Objects.requireNonNull就用到了一个java的核心思想“fail as fast”,即最快抛出错误的思想。而这个相关的思想其余博主也做了很详细的整理,笔者在此一并给出(后续自己也会整理)

  1. fail-fast思想博客一
  2. fail-fast思想博客二

即我们可以在创建对象的时候,就判断该对象是否为空,而非在其他方法调用该对象的时候,才对其进行检查是否为非空。(假如调用对象的方法在程序的很后期,那么我们得等到程序运行到很后期才会报错)。

此外,也感谢国外的一篇讨论内容,强烈推荐各位小伙伴去学习一下(国内的很多篇博客均是翻译的此篇,如小伙伴们有需要,我也可以将其整理成可靠的翻译文档)

为什么我们要使用Objects.requireNonNull方法的讨论


题外语

笔者查看了很多很多很多关于Objects.requireNonNull的介绍,里面均提及了这个思想,但并未举出很合适的例子,也未能剖析其真正的作用和原理,不得不感叹许多人水文章的实力和对知识不屑一顾的态度(很多人只是机翻了别人网站的讨论内容),在这里,笔者完成此篇博客,既以奖励自己一次深挖的成功,又以提醒自己不要变成别人一样半知不解而好为人师的人。

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我想脱离小码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值