57

当有后置条件时,方法的返回值不能为空,怎么办?

我可以做

assert returnValue != null : "Not acceptable null value";

但断言可以关闭!

那么可以这样做吗

if(returnValue==null)
      {
           throw new NullPointerException("return value is null at method AAA");
      }

?

还是对这种情况使用用户定义的异常(如 NullReturnValueException )更好?

4

19 回答 19

71

我建议你永远不要NullPointerException自己扔。

正如 Thorbjørn Ravn Andersen 在下面的评论中所说,不这样做的主要原因是您不想将“真正的、糟糕的 NPE”与故意抛出的 NPE 混合在一起。

因此,在您确信自己能够识别“有效”NPE 之前,我建议IllegalArgumentException在您想告诉 API 用户这null不是有效的参数值时使用。应该记录您的方法在传递非法空参数时的行为。

另一个(更现代的恕我直言)选项是@NotNull在参数附近使用注释。这是一篇关于使用 @NotNull 注释的文章

正如我之前提到的,在某些情况下,抛出 NPE 不会让你或你的队友感到困惑:NPE 的原因应该是清晰和可识别的。

例如,如果您使用一些带有前置条件模块的库,例如Guava,那么我发现使用checkNotNull()-like 方法是处理非法传递的空值的更好方法。

checkNotNull(arg, msg)抛出 NPE,但从堆栈跟踪很清楚,它是由产生的Preconditions.checkNotNull(),因此它不是一个未知的错误,而是预期的行为。

于 2010-07-23T23:06:40.740 回答
44

我认为在 JVM 为您执行 NPE 之前尽早抛出 NPE 没有问题 - 特别是对于空参数。关于这一点似乎存在一些争论,但 Java SE 库中有很多例子就是这样做的。我不明白为什么 NPE 在你不能自己扔的方面是神圣的。

然而,我离题了。这个问题是关于不同的东西。您正在谈论一个说明返回值不能为空的后置条件。在这种情况下肯定 null 意味着您在方法内部有一个错误?

你甚至会如何记录这一点?“如果返回值意外为空,此方法会抛出 NullPointerException”?不解释这是怎么发生的?不,我会在这里使用断言。异常应该用于可能发生的错误——而不是覆盖如果方法内部有问题可能发生的事情,因为这对任何人都没有帮助。

于 2010-07-24T23:10:16.347 回答
29

鉴于这NullPointerException是在 Java 中传达意外 null 值的惯用方式,我建议您抛出一个标准NullPointerException,而不是自己开发的标准。还要记住,最小意外原则建议您不要为存在系统异常类型的情况发明自己的异常类型。

断言有利于调试,但如果您必须处理某些条件则不好,因此这不是处理错误条件的真正好方法。

于 2010-07-23T21:49:57.507 回答
15

问题NullPointerException在于,当您忘记检查某些内容是否为 null 或给出错误的参数为 null 时,就会发生这种情况,并且不应该这样做。

根据我的经验,Java 程序员很快就知道这个异常是由代码中的错误引起的,所以手动抛出它会让他们中的大多数人感到非常困惑。IllegalArgumentException当您传递不可接受的参数(例如 null,其中某些内容不能为 null)时,这是一个更好的主意。

它还触发了另一种启发式方法。NPE = 有人在此处的代码中出错,IllegalArgumentException= 赋予该方法的对象无效。

另一方面,javadoc 告诉:

应用程序应抛出此类的实例以指示对象
的其他非法用途null

所以抛出 NPE 是合法的,但这不是常见的做法,所以我建议IllegalArgumentException

于 2013-03-22T16:04:50.903 回答
8

当然没有一个普遍的规律来反对抛出 NullPointerException,但如果你真的应该在这样一个抽象的例子中回答,那就很难回答了。您不想做的是将人们置于试图捕获 NullPointerException 的位置。像这样的代码(真实的例子,我发誓):

catch (NullPointerException npe) {
  if (npe.getMessage().equals("Null return value from getProdByCode") {
    drawToUser("Unable to find a product for the product type code you entered");
  } 
}

是一个万无一失的指标,你做错了什么。因此,如果 null 返回值是您实际能够传达的某些系统状态的指示符,请使用传达该状态的异常。没有多少情况我能想到对引用进行空值检查以丢弃空指针是有意义的。通常,下一行代码无论如何都会丢弃空指针(或更多信息)!

于 2010-07-23T22:12:23.393 回答
6

http://pmd.sourceforge.net/pmd-5.0.1/rules/java/strictexception.html
“避免抛出 NullPointerExceptions。这些是令人困惑的,因为大多数人会认为虚拟机抛出了它。考虑使用 IllegalArgumentException 代替;这个将被清楚地视为程序员引发的异常。”

于 2013-03-22T15:31:19.020 回答
5

如果您记得描述,我会认为 NullPointerException 的用法没问题。这就是调查人员所使用的(行号可能会发生变化)。还要记住记录您的方法在特殊情况下会引发空指针异常。

如果你一开始就检查你的方法参数,throw new IllegalArgumentException("foo==null")我也可以接受。

于 2010-07-23T23:00:18.393 回答
4

如果你描述了一个不能返回值的方法契约null,那么你最好确保你不返回null。但这根本不是 NullPointerException。如果您必须返回的值null显然是调用者给了您错误的参数(IllegalArgumentException),您没有处于有效状态(IllegalStateException),或者发生了除 NullPointerException 之外的其他更有意义的异常情况(这通常表明编程错误)。

于 2010-07-24T06:32:21.847 回答
3

我称为O'Reilly 的 Java in A Nutshell 的一本书是由专家编写的,它列出了 NullPointerException 的定义:

表示尝试访问字段或调用空对象的方法。

由于返回 null 不是其中任何一个,我认为编写自己的异常更合适。

于 2010-07-23T21:51:08.773 回答
3

NullPointerException的JavaDoc指出:

当应用程序在需要对象的情况下尝试使用 null 时引发。这些包括:

* Calling the instance method of a null object.
* Accessing or modifying the field of a null object.
* Taking the length of null as if it were an array.
* Accessing or modifying the slots of null as if it were an array.
* Throwing null as if it were a Throwable value. 

应用程序应抛出此类的实例以指示空对象的其他非法使用。

我认为违反后置条件是非法行为。但是,我认为您使用什么异常并不重要,因为我们正在讨论一个应该(并且希望是)无法访问的代码路径,因此您不会有特定于该异常的错误处理,因此唯一的效果该名称是日志文件中某些条目的不同措辞,没有人可能会看到。

相反,如果您认为后置条件可能会被违反,那么包含更多调试信息可能是个好主意,例如调用该方法的参数。

于 2010-07-23T22:03:50.533 回答
2

绝对没错。

甚至 JDK7 也解决了这个问题。见对象#requireNonNull

void doWith(final Object mustBeNotNull) {

    /*
    // bush style
    if (mustBeNotNull == null) {
        throw new IllegalArgumentException("mustBeNotNull must not be null");
    }
    */

    /*
    // obama style
    if (mustBeNotNull == null) {
        throw new NullPointerException("mustBeNotNull must not be null");
    }
    */

    // kangnam style
    Objects.requireNonNull(mustBeNotNull, "mustBeNotNull must not be null");

    assert mustBeNotNull != null;
}
于 2013-02-08T06:04:24.133 回答
1

IMO 你永远不应该手动抛出 NullPointerException。如果不检查描述,调用例程将不知道是真实的还是手动的 NullPointerException。在这种情况下,您似乎希望将自己的异常与问题更接近,以便调用方法可以正确地从该异常中恢复。也许 PostConditionException 在许多情况下都足够通用。

于 2010-07-23T23:21:39.473 回答
1

在逻辑变得如此深入以至于调用程序员很难弄清楚什么是空之前抛出 NPE 通常是一个非常好的主意。addListener() 方法就是一个很好的例子。

尽管存在不知情的反对意见,但 JDK 中有许多方法可以做到这一点。

于 2010-07-24T10:34:18.050 回答
0

在 Java-verse 中,当期望一个对象时,null 始终是一个有效值。你最好避免这种不可能的后置条件。如果你真的不能忍受 null 那么你将不得不重新设计你的方法,这样你就可以返回一个原语。

于 2010-07-24T07:47:48.850 回答
0

正如 Joshua Bloch 曾经说过的:“Null 糟透了!” :) 每当我的脸是 null 时,我都会尝试使用 guava 提供的 Optional。对我来说优点很多。

于 2013-02-18T11:40:53.790 回答
0

当有后置条件时,方法的返回值不能为空,怎么办?

后置条件意味着如果不满足条件,则相关方法存在错误。在代码中表达这一点的方法是使用assert后置条件。直接抛出异常,例如 aNullPointerException或 anIllegalStateException会有点误导,因此会被误导。

以编程方式抛出 NullPointerException 可以吗?

NPE 的 Java API 文档说是,但是,从这个页面上给出的投票来看,3:1 的大多数开发人员说不。所以我想说这取决于您工作组中的约定。

API 文档首先列出了 JVM 引发 NPE 的情况,因为代码尝试在空引用上调用操作,该操作需要某种类型的对象(如调用方法或访问字段),而null不是对象。然后它说:

应用程序应抛出此类的实例以指示null对象的其他非法用途。

有趣的是,null在这里被称为“对象”,但它不是。这提醒我,NullPointerException对于一种没有指针的语言来说,这个名字很奇怪。(这可能NullReferenceException与 Microsoft .NET 类库中的一样。)

那么我们应该在这个计数上忽略 API 文档吗?我不这么认为。类库确实使用了文档中描述的 NPE,例如java.nio.channels

除非另有说明,否则将null参数传递给此包中任何类或接口中的构造函数或方法将导致 aNullPointerException被抛出。

这不是 JVM 生成的 NPE,而是带有附加错误消息的编码 NPE,说明哪个参数是null(如"in" is null!)。(代码可以通过做javap -c -p java.nio.channels.Channels | more、寻找来查看private static void checkNotNull。)而且有很多类以这种方式使用 NPE,本质上是IllegalArgumentException.

因此,在对此进行了一些调查和思考之后,我发现这是对 NPE 的一个很好的使用,因此我同意 API 文档和少数 Java 开发人员(根据本页上的投票),你们都是以与 Java 类库相同的方式在您自己的代码中使用 NPE 的权利和权利,即提供一条错误消息,这在 JVM 生成的 NPE 中明显缺失,这就是为什么告诉这两种 NPE 没有问题分开。

为了解决 NPE 无论如何都会被扔得更远的小问题:尽早发现错误而不是让 JVM 继续执行程序是非常有意义的,这可能涉及磁盘或网络 I/O(以及延迟),并生成不必要的大堆栈跟踪。

于 2014-02-03T13:21:56.057 回答
0

是的,没关系,但我认为让它发生是一个更好的决定。

Java 程序员和 null 的问题在于人们来自 C/C++ 背景,其中 NULL 意味着很多不同的东西。在 C/C++ 中,取消引用 NULL(或 Wild)指针是一个严重的问题,可能会导致奇怪的内存问题或使您的程序崩溃(显然是不可取的)。如果您能够摆脱 C/C++ 的思维方式,并且意识到您有这个额外的层,即 JVM,它会为您处理这种情况,您就会开始对 NULL 有一点不同的看法。

例如,在 C++ 中,我们有永远不能赋值为 NULL 的引用。在 Java 中,没有引用,但 Java 对象参数的行为更像 C++ 指针。但是在 Java 中有很多情况,其中方法隐式不应该接收参数的空值!那么我们该怎么办?

像在 C++ 中那样在 Java 中处理 null 的问题在于,这会导致到处检查 null 而在 C++ 中,您只需声明一个获取引用的方法,该方法明确声明它不接受 NULL。很快,每个方法都必须在其中进行健全性检查,以便在那时断言程序的条件,从而造成混乱。

在方法的默认约定是 null 对参数值无效的假设下工作是一种更好的心态。

为什么?好吧,让我们看看当这样的方法接收 null 作为参数值时会发生什么。a) 也许没关系,因为它不会取消引用该值作为其行为的一部分。在这种情况下,什么都不会发生。b) 该值被取消引用。在这种情况下,来自 JVM 的行为正是我们想要的:抛出一个异常,表明由于参数值为 null 而违反了方法的约定,并且它包含一个堆栈跟踪,将我们一直带到以这种方式使用该值的方法中的行。

人们对 NPE 提出质疑,因为他们认为当您在日志中看到 NPE 时,这意味着“有人搞砸了”。但是,让我们考虑一下。与预期行为相比,NPE 作为一个糟糕的指标实际上有什么区别?我认为使用 NPE 作为预期行为的主要区别(和优势)在于它指向的不是发生它的方法,而是指向违反方法合同的调用者。这是更有用的信息。如果我们只是简单地检查 null 并抛出一个不同的异常,我们可能会误以为观察到的行为是预期的错误,而实际上调用者违反了方法的约定。恭喜,您正确地预料到了调用者在调用该方法时可能会搞砸的方式——不管你怎么做

因此,归根结底,人们认为 NPE 是禁忌。从字面上看,人们不会允许它被抛出,因为它会带来一些羞耻感——就好像你不够聪明,因为你没有猜到一个值会在哪里为空。好吧,我有消息告诉你们,你们只是在编写更多无用的代码来做同样的事情。

一些例子:

public void foo(Object o) {
  if (o == null) {
    throw new IGotchaException("HA! You're an IDIOT! I knew it!!");
  }
  o.bar();
}

public void foo(Object o) {
  o.bar();
}

^ 政治不同。从功能上讲,没有那么多。

public void foo(int a, long b, double c, Object o) {
  if (o == null) {
    throw new IllegalArgumentException("Oh. Uh. Well, you've passed me null here. I... I'm not sure where to go from here because this object is kind of required for this function to do what it's supposed to do. Soooo... wouldja mind reworking your code a little bit so as to not pass null to this function as a parameter?! That'd be great thanks. Oh by the way, it's cause we deference o 40 lines below here");
  }
  // ...
  o.doSomethingWithA(a);
}

public void foo(int a, long b, double c, Object o) {
  // ...
  o.doSomethingWithA(a);
  // NullPointerException, line 40, e.g. it wasn't OK to pass null for o you lunkhead
}

^ 也许以大量烦人的代码为代价节省了几个 CPU 周期。但是,我们在第二种情况下进行的比较较少。

public void foo(Object a, Object b, Object c, Object d) {
  if (a == null) throw IllegalArgumentException("jackass");
  if (b == null) throw IllegalArgumentException("jackass");
  if (c == null) throw IllegalArgumentException("jackass");
  // But d is totally OK!
  // ...
  c.setSomeValueThatMayBeNull(d);
}

public void foo(Object a, Object b, Object c, Object d) {
  // ...
  c.setSomeValueThatMayBeNull(d);
  // Throws null pointer exception if c is null, but not if d is null. Which is basically the same as above
}

^ 声明中隐含了契约,而不是方法开头的异常情况。没有其他缺点。

public void foo(Object o) {
  if (o == null) {
    doTheBoogie();
  } else {
    doTheRobot();
  }
}

^ 不好

public void foo(Object o, int b) {
  Bar value = o.findSomethingWhichMayExist(b);
  if (value == null)
    return;
  value.doSomething();
}

^ 使用空返回值表示没有值。好的。

人们遇到 NPE 问题的另一个原因是他们不知道如何处理异常。NPE 永远不应该成为阻碍者。适当的行为是捕获 RuntimeException,大概是在调用堆栈中更高(或更低,取决于您如何看待它)的级别,该级别捕获它并在“main”之前报告它。也就是说,假设您正在开发的程序需要更有弹性,并且不能在 NPE 发生时崩溃。

底线:永远不要期望为方法参数传递空值是有效的。并且绝对不要为明确接受 null 并将其视为有效值的方法创建合同。但是,允许发生空指针异常,自然而然地让代码失败,或者在无关紧要时不失败。

于 2017-06-29T10:08:13.610 回答
0

我同意前面的答案中的说法,即 NPE 是代码中的错误,不应该被抛出,但开发人员应该修复意外的 null。但是,大多数情况下可以通过测试来防止这种状态。

这个问题是在 7 年前提出的,但现在我们在 java 8 中有 Optional,这个特性可以防止 NPE。

我想到的最后一个解决方案是,您应该检查 null 上的对象,如果它相等,那么抛出您自己的异常并描述所发生的事情。

于 2017-10-04T18:51:33.070 回答
-1

在某些情况下抛出该异常不是一个好习惯,我想知道为什么当您已经使用 if 语句捕获它时?

如果(返回值==空)

于 2010-07-24T01:53:47.983 回答