【问题标题】:What is the best way to perform a null check WITHOUT the NullPointerException being thrown?在不抛出 NullPointerException 的情况下执行空检查的最佳方法是什么?
【发布时间】:2011-09-04 10:36:25
【问题描述】:

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

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

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

【问题讨论】:

  • 向我们展示getLabels() 代码。
  • 如果可以的话,发布你的 getLabels() 方法和你的 NPE
  • 如果这个简单的比较抛出 NPE,也许你最好检查一下 aPost 是否为空。我认为,在比较之前抛出的 NPE。
  • @Nupul:您阅读给出的正确答案了吗?他们告诉你 aPost 是空的。我知道你说不是,但你做过检查吗?一个简单的System.out.println("aPost is null: " + (aPost == null)); 将在抛出 NPE 的行之前完成。
  • @Hovercraft:在下面回复您的评论。是的,我已经添加了支票。它总是打印为非空:)

标签: java nullpointerexception


【解决方案1】:

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

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

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

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

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

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

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

【讨论】:

  • aPost 根本不为空。它实际上是在继续执行代码之前创建的。但似乎代码中有一个地方 if-check 是 OR if(aPost.getLabels()!=null || !aPost.getLabels().isEmpty()) 似乎条件不是短路,而是试图评估两者,因此 NPE ......但我不完全确定这个目前还在评估中……已经更新了我的 OP
  • 好吧,如果您发布的代码与您实际拥有的代码不同,如果您没有得到正确答案,请不要感到惊讶。查看更新的版本,我可以看到您只是操作错误。它应该是&amp;&amp; 而不是||。 Java 短路工作正常。
  • 嗯...似乎有道理 ~(A || B) = ~A && ~B 猜猜我们完全忽略了基本的德摩根定律!仍在测试它...会回来
  • @Nupul:除非您检查它,否则您不能说 aPost 不为空。请在您看到 NPE 之前输入一行代码来检查这一点,然后告诉我们您得到了什么。
  • @Hovercraft:我确实添加了 if(aPost==null) s.o.p("aPost NULL") else s.o.p("aPost NOT NULL") 行输出始终不是 NULL
【解决方案2】:

更好的编码方法是始终初始化数组,而不是简单地迭代列表,而不是空检查,在空的情况下不会做任何事情,因为它是空的(不是空的)。

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,也不必使用看起来令人讨厌的空值检查代码,您只需依靠空列表不会迭代这一事实。

或者(正如我在我的 cmets 中所说)如果您必须延迟实例化 List,请执行此操作,但始终返回一个可以迭代的有效 List 对象,方法是将 getLabels() 更改为

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

这意味着调用 getLabels() 的任何方法都不需要检查空对象,它们可以简单地遍历返回的对象,这大概就是您最终将对列表执行的操作。这确实提高了代码的可靠性和可读性。我必须查看很多代码,这些代码具有这种在访问对象之前总是检查空值的方法,这些都可以通过确保返回的对象作为其名称而不是可能为空来清除。

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

【讨论】:

  • 对不起,这不是真的。
  • @Mark:这就是延迟加载的方式。如果它确实为空,则空检查会捕获它......问题是它没有被捕获
  • @biziclop 什么不是真的?我的帖子的重点是,如果你给它一个空数组开始,你不需要任何空值检查,而且我认为在数组上简单地使用for() 比放置一个简单的if( a!=null &amp;&amp; a.b.isEmpty()) 条件看起来更干净不再需要了。
  • @nupul - 我理解延迟加载,正如我所说,我正在展示如何编写 OP 的代码而无需任何不会抛出 NPE 的空检查。如果数组中没有标签,循环的迭代将跳过,对我来说,这是空检查的首选样式。
  • @biziclop:您应该在系统边界检查空值,因为您无法确保来自外部代码的值的非空值。但是检查眼前的一切是没有意义的。这只是不必要的代码,因此是糟糕的编程。
【解决方案3】:

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

一个空指针异常抛出一个形式的表达式

A.method()

A.field

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

这样的语句中
a.b.c.d.e().f.g 

如果 a 为 null 或 a.b 为 null,或 a.b.c 为 null,则抛出空指针异常,依此类推:点左侧的某些内容为 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." );

问候, 斯蒂芬

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-22
    • 1970-01-01
    • 2010-10-29
    相关资源
    最近更新 更多