繁体   English   中英

在不引发NullPointerException的情况下执行空检查的最佳方法是什么?

[英]What is the best way to perform a null check WITHOUT the NullPointerException being thrown?

所以我知道我可能有一个空列表(特别是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

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

通常,调试NPE时要做的第一件事是仔细检查堆栈跟踪并确定从中抛出的确切行。

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

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

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

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

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

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

更好的编码方式是始终初始化数组,而不是进行空检查,而只是遍历列表,在空情况下,因为空(不为空),所以什么也不做。

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,并且不必再麻烦地查找null检查代码,您只需依靠一个事实,那就是空列表不会迭代。

另外(如我在评论中所述),如果您必须延迟实例化List,请执行此操作,但始终将可迭代的有效List对象更改为getLabels(),

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

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

编辑:OP更新了有关使用的实际if语句的帖子后,删除了有关getLabels()的部分,并添加了有关使List充当List的注释。

您实际上缺少了一些东西,尽管您非常接近想要的东西:

在以下形式的表达式中抛出空指针异常

A.method()

A.field

如果A为null。 这意味着在类似

a.b.c.d.e().f.g 

如果a为null或ab为null或abc为null,则抛出null指针异常,依此类推:点左侧的内容为null。

因此,在您的示例中,如果您在执行操作时遇到异常

if(aPost.getLabels()!=null)

唯一的解决方案是aPost为null。 没有其他的。

而且实际上您是对的,要知道某项是否为null,最好使用等号(==)将其与null进行比较

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

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

问候,斯特凡

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM