在没有抛出NullPointerException的情况下执行空检查的最佳方法是什么?

时间:2011-08-02 10:21:10

标签: java nullpointerexception

所以我知道我可能有一个空List(特别是ArrayList)。现在对它进行简单检查就像在我检查它时实际抛出NullPointerException一样。这个让我难过,因为我一直都很成功地使用它,但我确信我错过了一些东西:

public class MyPost {

private int id;

private List<Label> labels;

public MyPost(int id){ this.id = id }

//getter setters for both plus this added method:

public void addLabel(Label aLabel)
  {
     if(labels == null)
       labels = new ArrayList<Label>();

     labels.add(aLabel);
  }

}

现在在我的代码的另一部分中,我正在迭代客户端发送的ID列表。为简单起见,假设循环变量'i'正在提供ID

MyPost aPost = new MyPost(i);

在我的逻辑中,我可能会也可能不会在帖子中添加标签。所以在继续之前我会检查是否存在这样的标签:

if(aPost.getLabels()!=null)
   //process labels

现在,如果没有添加任何标签列表,则会抛出空指针异常!但这正是我想要检查的内容,我仍在获得NPE !!!

我知道aPost.getLabels()如果没有添加任何内容则为null。但这种比较似乎失败了,并引发了一场NPE。如何解决这个问题?只是让我难过!

更新 这是获取标签代码。只是一个微不足道的吸气鬼......

public List<Label> getLabels() { return labels;}

我们注意到以前忽略过的东西。我确定java用来“短路”它的if条件,即在OR条件下,如果第一个条件被评估为true,它将不会检查第二个条件(如果第一个条件被评估为false,则类似于AND的短路) )。我不完全确定这是否是原因,但这里是if子句:

if(aPost.getLabels()!=null || !aPost.getLabels().isEmpty())
//process labels

如果列表确实为空,则短路不应评估第二个条件,正确吗?似乎可能是原因,但我们仍在测试它。现在只是预感......

3 个答案:

答案 0 :(得分:10)

一般来说,调试NPE时要做的第一件事就是仔细检查堆栈跟踪,并确定抛出它的确切行。

下一步是检查该行上解除引用运算符(.)的所有左侧值。 NPE的另一个来源是for循环的新风格,第三个是自动拆箱,据我所知,没有其他结构固有地抛出NPE,尽管当然总有代码明确抛出它。

所有这些意味着没有堆栈跟踪和完整代码,我们也只能猜测。

(或者,如果你正在使用IDE,你可以简单地设置一个异常断点,并在抛出NPE时检查变量的运行时值。但你应该能够通过离线分析找到一个NPE。代码和堆栈跟踪。这是一项重要的技能。)

更新:查看更新后的问题,显然if语句错误。它应该是:

if(aPost.getLabels()!=null && !aPost.getLabels().isEmpty())
//process labels

OR不是正确的操作,因为您希望aPost.getLabels()不为空且不为空。一旦知道了值,Java确实会停止布尔表达式求值,但在原始表达式中,如果aPost.getLabels()为空则不会出现这种情况。

答案 1 :(得分:3)

更好的代码编写方法是始终初始化数组,而不是空检查只是遍历列表,在空的情况下,它将无效,因为它是空的(非空)。

public class MyPost {
    private int id;
    private List<Label> labels = new ArrayList<Label>;
    public MyPost(int id){ this.id = id }

    //getter setters for both plus this added method:
    public void addLabel(Label aLabel) {
        labels.add(aLabel);
    }
}

// then later...
public void someProcessing() {
    for (Label label: labels) {
        // process label here
    }
}

现在你不再拥有NPE,并且你不必拥有令人讨厌的空检查代码,你只需要依赖空列表不会迭代的事实。

或者(正如我在评论中所说)如果你必须懒惰地实例化List,那么这样做但总是返回一个可以迭代的有效List对象,方法是将getLabels()更改为

public List<Label> getLabels() {
    return labels == null ? Collections.emptyList() : labels
}

这意味着没有调用getLabels()的方法需要检查空对象,它们可以简单地遍历返回的对象,这可能是你最终会最终对列表做的事情。这确实提高了代码可靠性和可读性。我必须回顾很多代码,这些代码总是在访问对象之前检查空值,这样可以通过确保返回的对象充当其名称而不是可能为空来清除所有代码。

编辑:在OP更新帖子后删除关于所使用的实际if语句的关于getLabels()的部分,并添加关于使列表作为列表的注释。

答案 2 :(得分:1)

你实际上错过了什么,虽然你非常接近你想要的东西:

在表单

的表达式中抛出空指针异常
A.method()

A.field

如果A为空。这意味着在像

这样的声明中
a.b.c.d.e().f.g 

如果a为null或a.b为null,或者a.b.c为null,则抛出空指针异常,依此类推:点左侧的某些内容为空。

因此,在您的示例中,如果您在执行

时遇到异常
if(aPost.getLabels()!=null)

唯一的解决方案是aPost为null。没别了。

实际上你是对的,要知道某些东西是否为空,最好用等号(==)将它与null比较

将以下内容添加到您的代码中:

if( aPost == null )
   System.out.println( "Oh, aPost is null itself and my bug is not related to its fields being null." );

此致  斯特凡