在Java中检查空值的最佳方法是什么?

115

在调用对象的函数之前,我需要检查该对象是否为null,以避免抛出NullPointerException

什么是最好的做法?我考虑了以下方法。
哪一个是Java最佳编程实践?

// Method 1
if (foo != null) {
    if (foo.bar()) {
        etc...
    }
}

// Method 2
if (foo != null ? foo.bar() : false) {
    etc...
}

// Method 3
try {
    if (foo.bar()) {
        etc...
    }
} catch (NullPointerException e) {
}

// Method 4 -- Would this work, or would it still call foo.bar()?
if (foo != null && foo.bar()) {
    etc...
}

13
不要捕获空指针异常。这属于“蠢笨异常”类别。http://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx - Nick Freeman
1
根据您的使用情况,禁止foonull值也是有意义的。如果传递了nullfoo到您的方法中,则抛出NullPointerException异常。 - assylias
1
  1. 1和4之间的区别在于风格选择,最多只是微小的优化。
  2. 只要不允许异常被抛出,实际上并不重要。与其担心哪种方法更好,你的时间更好花在清晰的设计/更好的算法上。
- Nick Freeman
2
@assylias 在那种情况下应该使用“IllegalArgumentException”。 - Nick Freeman
4
@NickFreeman 我不同意 - 例如,可以参考这个链接:https://dev59.com/HHVD5IYBdhLWcg3wXaYd#8196334 - 标准做法(在JDK、Guava和Effective Java中)是抛出NPE异常。尽管在这种情况下IAE也被广泛使用。 - assylias
显示剩余4条评论
19个回答

154

方法4是最好的。

if(foo != null && foo.bar()) {
   someStuff();
}

将使用短路评估,这意味着如果逻辑 AND的第一个条件为false,则结束。


4
谢谢,我觉得那样做是最好的,但我不确定为什么它没有满足第二个条件,或者是否有时会满足 - 感谢你解释原因。 - JamieGL
1
是的,因为您正在使用短路运算符&&,所以它不会完成整个表达式。 - Ahmed Hamed
@Arty-fishL 这是一个很好的问题,因为它取决于编程语言 - 通常 类C语言倾向于采用从左到右的短路计算,但并非所有语言都是如此,最好始终先进行检查 ;) - jave.web

19

最后也是最好的一个,即逻辑与

  if (foo != null && foo.bar()) {
    etc...
}

因为在逻辑上 && 运算符,只要结果是假的,就不需要知道右侧是什么。

更喜欢阅读:Java逻辑运算符短路


使用这种方法,我发现自己得到了“运算符&&不能应用于布尔值”的错误提示。if (object.getObjectMethod() != null && object.getObjectMethod().getNextThing()) - Kevin Crum

16

3
自Java 8以来,此功能已可用。 - bluelurker
1
已更新答案 :) - forhadmethun
如果你阅读了这个方法的文档,你就会知道它不应该在if语句中使用,它被设计成过滤器的谓词。 - undefined

7
  • 不要捕获NullPointerException,这是一种不好的做法。更好的方法是确保值不为null。
  • 第4种方法将适用于您。它不会评估第二个条件,因为Java具有短路效应(即,如果它们不改变布尔表达式的最终结果,则不会评估后续条件)。在这种情况下,如果逻辑AND的第一个表达式评估为false,则不需要评估后续表达式。

1
值得一提的是,捕获空指针异常为什么是不好的实践;异常非常非常昂贵,即使只有几个也会严重拖慢您的代码。 - Richard Tingle
2
在大多数情况下,异常的成本是微不足道的(Java 在各个地方都使用它们),更重要的是 NullpointerException 可能不仅仅来自 foo,而是可能在 try{ 和 }catch 之间的任何地方产生,因此通过捕获它可能会隐藏一个错误。 - josefx
@josefx 做(几乎)任何事情的成本都微不足道,除非它所在的代码部分是瓶颈;但如果是这样,那么它很可能是原因。我收集了一些数据来回答这个问题https://dev59.com/KWQo5IYBdhLWcg3wGsPN#16320082。显然,出于你提到的原因(以及其他许多原因),这也是一个坏主意。也许我应该说“其中之一”的原因,而不是“唯一”的原因。 - Richard Tingle
1
@RichardTingle,异常不像以前那么昂贵了(特别是如果您不需要检查堆栈跟踪)。除此之外,我同意 - 然而,对我来说,原因是异常会打破读者所期望的程序流程。 - Thorbjørn Ravn Andersen

5

Java 7中,您可以使用Objects.requireNonNull()方法。需要从java.util中导入Objects类。

public class FooClass {
    //...
    public void acceptFoo(Foo obj) {
        //If obj is null, NPE is thrown
        Objects.requireNonNull(obj).bar(); //or better requireNonNull(obj, "obj is null");
    }
    //...
}

一些原因,SO不允许我将 import java.util.Objects; 这行代码添加到程序中,所以在这里将其放在注释中。 - KrishPrabakar
3
如果使用Java 8,您可以尝试使用Optional类。 - KrishPrabakar
如果它为空,将会抛出一个 NullPointer。 - Marcel

5

方法4是最好的,因为它清楚地指示了会发生什么,并且使用了最少的代码。

方法3在各个层面上都是错误的。您知道该项可能为空,因此这不是异常情况,而是应该检查的内容。

方法2只是使它比必要的更加复杂。

方法1只是方法4多了一行代码。


方法3是Python中通常会执行的。与其说“你知道该项可能为空,因此这不是异常情况”,不如说“你知道在异常情况下(例如文件在某处丢失),它将为空”。我不知道为什么Java开发人员这么害怕异常。如果你仍然需要到处检查返回值,那么添加异常能力有什么意义呢?在围绕代码加上try .. catch的情况下,是否有性能损失?是否有充分的理由,还是只是Java的风格? - rjmunro
@rjmunro 在Java中,异常比普通返回要昂贵得多。异常必须捕获堆栈跟踪,这相当昂贵,尽管我依稀记得几年前为那些使用异常来控制流程的人进行了优化。在Java中,异常通常用于非常特殊的情况,如果您希望从文件中读取内容,但文件不存在,则会抛出异常。至于空值检查,我有些同意,Java现在有许多处理空值的方法,例如新的Optional类。 - wobblycogs
在性能方面,我对异常发生的性能不感兴趣 - 最坏情况下,用户在收到错误消息前可能需要1或2秒钟 - 我对使用try { ... } catchif (foo != null) { ... }包围的性能感兴趣。假设值不为null,显式的空检查是否比try catch更快,并且如果您有几个可以为空的东西并将整个内容用try catch包围,或者需要try catch处理其他错误并添加额外的catch,这是否适用? - rjmunro
空值检查将至少快一千倍(可能是数千倍)。要使执行路径进入catch块,必须引发异常,这意味着生成堆栈跟踪等等。我不确定,但空值检查几乎肯定会转换为本机指令,因此它是最接近即时的。只要没有引发异常,try catch块对性能影响可以忽略不计。 - wobblycogs

3

正如其他人所说,当不使用库方法时,方法#4是最好的。但是你应该始终将null放在比较的左侧,以确保你不会在拼写错误的情况下意外将null赋值给foo。在这种情况下编译器会捕获错误。

// You meant to do this
if(foo != null){

// But you made a typo like this which will always evaluate to true
if(foo = null)

// Do the comparison in this way
if(null != foo)

// So if you make the mistake in this way the compiler will catch it
if(null = foo){

// obviously the typo is less obvious when doing an equality comparison but it's a good habit either way
if(foo == null){
if(foo =  null){

2
(foo = null) 不等同于 true。 - khelwood
在Java中,boolean是一种独立的类型,而不像C或C++中的int。因此,Java编译器会注意到当您使用int而不是boolean时,并抱怨类型不匹配。 - Anders

2

更新

NullUtil代码库

Java中处理空值的通用方法

<script src="https://gist.github.com/rcvaram/f1a1b89193baa1de39121386d5f865bc.js"></script>

  1. 如果该对象不为null,则我们将执行以下操作:

    a. 我们可以修改对象(I)

    b. 我们可以返回输出结果(O)而不是修改对象(I)

    c. 我们可以同时进行

在这种情况下,我们需要传递一个函数,该函数需要接受输入参数(I),该参数是我们的对象。如果我们以这种方式进行,那么我们可以在需要时更改该对象,该函数也可能返回某些内容(O)。

  1. 如果一个对象为null,则我们将执行以下操作

    a. 我们可以按自定义方式引发异常

    b. 我们可以返回某些内容。

在这种情况下,对象为null,所以我们需要提供值,或者我们可能需要抛出异常。

我举两个例子。

  1. 如果我想要在字符串中执行trim操作,则该字符串不应为null。在这种情况下,我们必须额外检查null值,否则会出现NullPointerException。
public String trimValue(String s){
   return s == null ? null : s.trim();
}
  1. 如果对象不为空,我希望设置一个新值到该对象中,否则我想抛出运行时异常的另一个函数。
public void setTeacherAge(Teacher teacher, int age){
   if (teacher != null){
      teacher.setAge(age);
   } else{
      throw new RuntimeException("teacher is null")
    }
}

通过我的解释,我创建了一个通用方法,该方法接受一个值(该值可能为null),一个函数(如果对象不为null则执行)和另一个供应商函数(如果对象为null则执行)。

通用函数

  public <I, O> O setNullCheckExecutor(I value, Function<I, O> nonNullExecutor, Supplier<O> nullExecutor) {
        return value != null ? nonNullExecutor.apply(value) : nullExecutor.get();
    }

有了这个通用函数之后,我们可以按照以下方式来处理示例方法: 1.

//To Trim a value
        String trimmedValue = setNullCheckExecutor(value, String::trim, () -> null);

这里,nonNullExecutor函数是对值进行修剪(使用方法引用)。nullExecutorFunction将返回null,因为它是一个身份函数。

2.

// mutate the object if not null otherwise throw a custom message runtime exception instead of NullPointerException
 setNullCheckExecutor(teacher, teacher -> {
            teacher.setAge(19);
            return null;
        }, () -> {
            throw new RuntimeException("Teacher is null");
        });

2
我认为在我查看的代码中,方法4是最通用的习语。但是这种做法总让我有点不舒服。它假定foo == null与foo.bar() == false是相同的。对我来说,这并不总是感觉正确。

2

我的首选方法是方法4。&&运算符的短路特性使代码更易读。通常情况下,方法3——捕获NullPointerException——被认为是不好的做法,简单的空值检查就足够了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接