有开发经验的人都知道 Java 中的空指针异常 NullPointerException(NPE),当我们试图使用一个值为 null 的对象引用时,就会抛出这个异常。
当执行上面的程序的时候,就会报NPE:
像上面这样的简单方法,我们根据堆栈信息,很快就能找到问题所在。但是实际开发中,空指针可谓无处不在,一不小心就会踩坑,而且有些 NPE 是 潜伏在程序当中 的,只有遇到某些特殊情况时才会出现, 很难测出来 。
那么,我们要 如何尽量避免 NPE 的发生 呢?总结起来,有以下 7 条,值得收藏!
这其实是句 正确的废话 !写 Java 的人都知道使用对象时有可能会出现空指针,所以 “最好都要记得” 判空,但关键是,如果都能记得的话,NPE 就不会那么难缠了。而且有些情况下,NPE 隐藏得很深,往往是在真正出问题的时候才会被发现。
提高判空意识,养成判空的直觉和习惯,肯定是最理想的解决方式。但是理想很性感,现实很骨感,人是不可靠的,因此我们可以 借助工具 来监督我们。
IDEA,就是一个非常不错的帮手,上面那段代码在 IDEA 的视角下,是这样的: 像这种 低级的错误 ,IDEA 会着重提醒你: 此处有 NPE 出没,请格外小心 !养成看警告的习惯比养成判空的习惯更容易些。
我们经常需要 判断两个对象是否相等 (equals),此时,我们可能会这么做:
这个方法里,有两个地方容易出现 NPE,
word.equals("haha")
word.equals(defaultVal)
对于第一个,通常有两种处理办法:
word!=null&&word.equals("haha")
"haha".equals(word)
第二种显然更受欢迎,然而对于两个都是变量的 word.equals(defaultVal)
比对,第二种方法就不适用了,需要这样判断:
想象一下,如果你的方法里有一堆这样的判断,会不会觉得烦,而且很容易走神 遗漏几个判空 ?
所以我推荐使用 Objects.equals(a,b)
方法,这样无论两个对象是否有 null 值的存在,都不必担心:
有一个 不容易发现但是非常容易出现 NPE 的场景:
这个方法,可能一眼很难发现其中的问题,而且正常使用的话也不会出问题。 直到有一天,你把 null 值传给了这个方法,悲剧就发生了 。
这就是 自动拆箱机制 导致的异常,在做 == 比较的时候,包装类 Integer 对象会自动拆箱为 int,当这个 Integer 对象是 null 值时,拆箱就会抛出 NPE。
这种场景下,最好的方法当然是尽量将方法参数改为 int:
但是如果你的方法调用者 确实有要处理包装类的需求 ,那么,调用的人就得小心了,他们同样会遇到自动拆箱问题: 这种情况下你的方法就得负责判空了。你也可以这样写:
注意, 只有你明确知道你的方法就是需要处理包装类的时候,才使用包装类做参数 ,
Objects 工具类为我们提供了几个相当不错的静态方法,使用起来可以大大降低 NPE 出现的概率。除了 Objects.equals
方法之外,
也是非常好用的方法!
实践中,对字符串进行判空是非常 高频的操作 。我们不仅要判断一个字符串是否为 null,而且还要知道它是否为空串,甚至还需要判断它是否为空白字符串。
新人容易犯的错误就是上来就直接判断:
if(str.trim().isEmpty())
NPE 就此产生! 这种场景下,一定要记得 str 有可能为空,所以正确的姿势应该是:
if(str==null||str.trim().isEmpty())
然而,还是想象一下,无处不在的这种散发着臭味的无聊判空,写着写着就会打起瞌睡来,然后遗漏掉一两个。还是那句话,人是不可靠的!
其实 commons-lang3 为我们提供了一些非常好用的方法,最常用的,就是 StringUtils.isEmpty/isBlank(str)
这两个用于判断字符串是否为空的方法了。无需担心 str 为空,尽管使用就可以了:
if(StringUtils.isBlank(str))
另一个类似的方法就是 字符串的大小写转换 了:
if(word.equals(str.toLowerCase()))
如果你经常这么写的话,肯定 天天被 NPE 缠身 。这样的表达式,稍微思考一下,你可能会知道进行判空:
OK,如果你这么写,至少不会报空指针了,然而,一个坑也就挖好了!可能几个月后的某一天,一个让你 死活查不出来的 Bug,让你通宵一整晚 !就因为你没有考虑到 word 和 str 都为 null 值的情况。所以正确的写法是:
好吧,这样无聊的代码,我实在编不下去了~~
于是小手一挥,我写下:
跟字符串判空一样,集合判空也是很常见的语句:
if(!list.isEmpty()){}
说了那么多,你还是这么写,NPE 估计爱死你了~
if(list!=null&&!list.isEmpty()){}
能不能更优雅些呢?Of course!
commons-lang3 的好哥们 commons-collections 为我们提供了对集合的一系列工具方法,其中就有判空的方法 CollectionUtils.isEmpty(list)
,因此,更优雅的写法是:
if(CollectionUtils.isNotEmpty(list)){}
有 isEmpty 当然就有 isNotEmpty 咯~
我们经常会定义一个返回一个集合的方法,像这样:
这样的方法定义,当然是没问题。但是如果调用你的方法的人是个大马哈,就会比较抓狂了。咱们做开发,还是要有点 用户思维 比较好。为了尽量避免给用户(其实是我们自己)带来不必要的麻烦,写方法时尽量不要返回 null 值,特别是当返回值是集合时,可以通过 返回空集合来避免空指针 。当然啦,总是 new 出一个空集合来,也是很影响性能的,因此我们可以使用: Collections.emptyMap/emptySet/emptyList();
来返回一个 全局共享的不可变的空集合:
注意咯,Collections.emptyList() 返回的是一个 不可变的空集合 ,不要妄想往里添加元素,否则会报错的!绝大多数情况下,我们不可能直接往方法返回的集合里添加元素的,所以放心使用就好。
作为调用者,我们需要采取 “ 不信任 ” 的原则,就算这个方法声称不会返回 null 值,我们在 处理返回值时仍要考虑 null 值 。鬼知道哪天会不会一个不小心有人把这个方法给改了,突然返回一个 null 值呢~
Optional 是 Java8 带来的新特性,它还算挺不错的,但是相较于其他语言,如 Grovvy 或 C# 对空指针的处理,这个 Optional 我感觉真是弱爆了。Grovvy 和 C# 中,有一个 .? 操作符,你只要 str?.toString()
就可以达到
str==null?null:str.toString()
相同的效果。但是 Java 则不行。
不过,毕竟 Optional 是 Java8 推出的解决 NPE 的利器,我们还是有必要学习一下的。特别是当 Optional 结合 Lambda 表达式的场景下会非常强大。具体可以参考这篇文章:http://weishu.me/2015/12/08/use-optional-avoid-nullpointexception/
假设我们有这样的代码:
显然,这里 soundcard 和 soundcard.getUSB() 都有可能为空,所以要进行判空:
使用 Optional 和 Lambda 表达式,可以免去这些判空:
这样,当 soundCard 为空,或者 soundCard.getUsb() 为空时, 什么事情都不会发生。只有当它们都不为空,而且 usb.getVersion() 为 3.0 时,才会打印 OK。
1. 意识:使用 obj.doSomething() 时记得判断 obj != null 。意识的养成需要一个漫长的过程,我们可以通过工具来帮忙,IDEA 就是一个非常出色的工具。
2. 判断对象是否相等时,使用 Objects.equals(a, b) ,当然 Objects 工具类还贴心地为我们提供了 toString 和 requireNonNull 这样的好帮手
3. 自动拆箱的陷阱 。当使用 包装类与原始类型 做比对时,要特别注意空指针问题
4. 检查字符串是否为空时,使用 commons-lang3 包 StringUtils 提供的isEmpty 和isBlank 方法 。另外, 使用 lowerCase 和 upperCase 进行字符串转换大小写转换,也可以避免空指针
5. 使用 commons-collections 包的 CollectionUtils.isEmpty 方法来 检查集合是否为空
6. 返回集合的接口若需要返回空,则 返回空集而不是 null 。但是每次都 new 出新的集合,会影响性能和不必要的对象创建,使用 Collections.emptyList(); 可以返回全局共享的不可变空集合
7. Optional 是 Java8 推出的解决 NPE 的利器, 当它跟 Lambda 表达式结合时会非常强大 。