java 为什么显式抛出 NullPointerException 而不是让它自然发生?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/43928556/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 07:49:58  来源:igfitidea点击:

Why explicitly throw a NullPointerException rather than letting it happen naturally?

javanullpointerexception

提问by LiJiaming

When reading JDK source code, I find it common that the author will check the parameters if they are null and then throw new NullPointerException() manually. Why do they do it? I think there's no need to do so since it will throw new NullPointerException() when it calls any method. (Here is some source code of HashMap, for instance :)

在阅读JDK源代码时,我发现通常作者会检查参数是否为空,然后手动抛出新的NullPointerException()。他们为什么这样做?我认为没有必要这样做,因为它在调用任何方法时都会抛出新的 NullPointerException()。(这里是一些HashMap的源代码,例如:)

public V computeIfPresent(K key,
                          BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    if (remappingFunction == null)
        throw new NullPointerException();
    Node<K,V> e; V oldValue;
    int hash = hash(key);
    if ((e = getNode(hash, key)) != null &&
        (oldValue = e.value) != null) {
        V v = remappingFunction.apply(key, oldValue);
        if (v != null) {
            e.value = v;
            afterNodeAccess(e);
            return v;
        }
        else
            removeNode(hash, key, null, false, true);
    }
    return null;
}

回答by shmosel

There are a number of reasons that come to mind, several being closely related:

有许多原因浮现在脑海中,其中几个原因密切相关:

Fail-fast:If it's going to fail, best to fail sooner rather than later. This allows problems to be caught closer to their source, making them easier to identify and recover from. It also avoids wasting CPU cycles on code that's bound to fail.

快速失败:如果要失败,最好早点失败。这允许更接近问题的源头发现问题,使它们更容易识别和恢复。它还避免将 CPU 周期浪费在必然会失败的代码上。

Intent:Throwing the exception explicitly makes it clear to maintainers that the error is there purposely and the author was aware of the consequences.

意图:明确抛出异常让维护者清楚地表明错误是故意存在的,并且作者知道后果。

Consistency:If the error were allowed to happen naturally, it might not occur in every scenario. If no mapping is found, for example, remappingFunctionwould never be used and the exception wouldn't be thrown. Validating input in advance allows for more deterministic behavior and clearer documentation.

一致性:如果允许错误自然发生,它可能不会在每个场景中都发生。例如,如果没有找到映射,则remappingFunction永远不会使用并且不会抛出异常。提前验证输入允许更确定的行为和更清晰的文档

Stability:Code evolves over time. Code that encounters an exception naturally might, after a bit of refactoring, cease to do so, or do so under different circumstances. Throwing it explicitly makes it less likely for behavior to change inadvertently.

稳定性:代码随着时间的推移而发展。遇到异常的代码自然可能会在稍微重构后停止,或者在不同情况下会停止。明确地抛出它可以减少行为无意中改变的可能性。

回答by David Conrad

It is for clarity, consistency, and to prevent extra, unnecessary work from being performed.

这是为了清晰、一致,并防止执行额外的、不必要的工作。

Consider what would happen if there wasn't a guard clause at the top of the method. It would always call hash(key)and getNode(hash, key)even when nullhad been passed in for the remappingFunctionbefore the NPE was thrown.

考虑如果方法顶部没有保护子句会发生什么。它总是会调用hash(key)getNode(hash, key)即使在抛出 NPE 之前null已经传入remappingFunction

Even worse, if the ifcondition is falsethen we take the elsebranch, which doesn't use the remappingFunctionat all, which means the method doesn't always throw NPE when a nullis passed; whether it does depends on the state of the map.

更糟糕的是,如果if条件是,false那么我们采用else根本不使用 the的分支remappingFunction,这意味着该方法在null传递a 时并不总是抛出 NPE ;是否确实取决于地图的状态。

Both scenarios are bad. If nullis not a valid value for remappingFunctionthe method should consistently throw an exception regardless of the internal state of the object at the time of the call, and it should do so without doing unnecessary work that is pointless given that it is just going to throw. Finally, it is a good principle of clean, clear code to have the guard right up front so that anyone reviewing the source code can readily see that it will do so.

这两种情况都很糟糕。如果null不是remappingFunction该方法的有效值,则无论调用时对象的内部状态如何,都应该始终抛出异常,并且应该这样做而不会做不必要的工作,因为它只是要抛出而毫无意义。最后,干净、清晰的代码是一个很好的原则,让守卫在前面,这样任何源代码的人都可以很容易地看到它会这样做。

Even if the exception were currently thrown by every branch of code, it is possible that a future revision of the code would change that. Performing the check at the beginning ensures it will definitely be performed.

即使当前每个代码分支都抛出了异常,未来的代码修订版也可能会改变它。在开始时执行检查确保它一定会被执行。

回答by Stephen C

In addition to the reasons listed by @shmosel's excellent answer ...

除了@shmosel 的优秀答案列出的原因之外......

Performance:There may be / have been performance benefits (on some JVMs) to throwing the NPE explicitly rather than letting the JVM do it.

性能:(在某些 JVM 上)显式抛出 NPE 而不是让 JVM 这样做可能会/已经有性能优势。

It depends on the strategy that the Java interpreter and JIT compiler take to detecting the dereferencing of null pointers. One strategy is to not test for null, but instead trap the SIGSEGV that happens when an instruction tries to access address 0. This is the fastest approach in the case where the reference is always valid, but it is expensivein the NPE case.

这取决于 Java 解释器和 JIT 编译器用来检测空指针解除引用的策略。一种策略是不测试空值,而是捕获在指令尝试访问地址 0 时发生的 SIGSEGV。这是在引用始终有效的情况下最快的方法,但在 NPE 情况下代价高昂

An explicit test for nullin the code would avoid the SIGSEGV performance hit in a scenario where NPEs were frequent.

null代码中的显式测试将避免在 NPE 频繁出现的情况下 SIGSEGV 性能受到影响。

(I doubt that this would be a worthwhile micro-optimization in a modern JVM, but it could have been in the past.)

(我怀疑这在现代 JVM 中是否值得进行微优化,但过去可能是这样。)



Compatibility:The likely reason that there is no message in the exception is for compatibility with NPEs that are thrown by the JVM itself. In a compliant Java implementation, an NPE thrown by the JVM has a nullmessage. (Android Java is different.)

兼容性:异常中没有消息的可能原因是与 JVM 本身抛出的 NPE 兼容。在兼容的 Java 实现中,JVM 抛出的 NPE 有一条null消息。(Android Java 是不同的。)

回答by EJoshuaS - Reinstate Monica

Apart from what other people have pointed out, it's worth noting the role of convention here. In C#, for example, you also have the same convention of explicitly raising an exception in cases like this, but it's specifically an ArgumentNullException, which is somewhat more specific. (The C# convention is that NullReferenceExceptionalwaysrepresents a bug of some kind - quite simply, it shouldn't everhappen in production code; granted, ArgumentNullExceptionusually does, too, but it could be a bug more along the line of "you don't understand how to use the library correctly" kind of bug).

除了其他人指出的之外,值得注意的是约定在这里的作用。例如,在 C# 中,您也有在这种情况下显式引发异常的相同约定,但它特别是 an ArgumentNullException,它更具体一些。(C#的惯例是,NullReferenceException始终代表了某种错误-很简单,它不应该永远发生在生产代码;授予,ArgumentNullException一般也有同样效果,但它可能是一个错误的更多沿着“你不这样做行了解如何正确使用库”类型的错误)。

So, basically, in C# NullReferenceExceptionmeans that your program actually tried to use it, whereas ArgumentNullExceptionit means that it recognized that the value was wrong and it didn't even bother to try to use it. The implications can actually be different (depending on the circumstances) because ArgumentNullExceptionmeans that the method in question didn't have side effects yet (since it failed the method preconditions).

因此,基本上,在 C# 中NullReferenceException意味着您的程序实际上尝试使用它,而ArgumentNullException这意味着它认识到该值是错误的并且它甚至懒得尝试使用它。含义实际上可能不同(取决于情况),因为这ArgumentNullException意味着所讨论的方法还没有副作用(因为它没有满足方法的先决条件)。

Incidentally, if you're raising something like ArgumentNullExceptionor IllegalArgumentException, that's part of the point of doing the check: you want a different exception than you'd "normally" get.

顺便说一句,如果您提出类似ArgumentNullExceptionor 的问题IllegalArgumentException,那就是进行检查的重点之一:您想要一个与“通常”得到的异常不同的异常。

Either way, explicitly raising the exception reinforces the good practice of being explicit about your method's pre-conditions and expected arguments, which makes the code easier to read, use, and maintain. If you didn't explicitly check for null, I don't know if it's because you thought that no one would ever pass a nullargument, you're counting it to throw the exception anyway, or you just forgot to check for that.

无论哪种方式,显式引发异常都加强了明确说明方法的前提条件和预期参数的良好做法,这使代码更易于阅读、使用和维护。如果您没有明确检查null,我不知道是不是因为您认为没有人会传递null参数,无论如何您都认为它会抛出异常,或者您只是忘记检查了。

回答by user207421

It is so you will get the exception as soon as you perpetrate the error, rather than later on when you're using the map and won't understand why it happened.

这样一来,一旦发生错误,您就会立即收到异常,而不是在您使用地图并且不明白为什么会发生这种情况之后。

回答by cmaster - reinstate monica

It turns a seemingly erratic error condition into a clear contract violation: The function has some preconditions for working correctly, so it checks them beforehand, enforcing them to be met.

它将看似不稳定的错误条件变成了明显的合同违规:该函数有一些正确工作的先决条件,因此它事先检查它们,强制满足它们。

The effect is, that you won't have to debug computeIfPresent()when you get the exception out of it. Once you see that the exception comes from the precondition check, you know that you called the function with an illegal argument. If the check were not there, you would need to exclude the possibility that there is some bug within computeIfPresent()itself that leads to the exception being thrown.

效果是,computeIfPresent()当您摆脱异常时,您将不必进行调试。一旦您看到异常来自前提条件检查,您就知道您使用非法参数调用了该函数。如果检查不存在,则需要排除computeIfPresent()自身内部存在导致抛出异常的错误的可能性。

Obviously, throwing the generic NullPointerExceptionis a really bad choice, as it does not signal a contract violation in and of itself. IllegalArgumentExceptionwould be a better choice.

显然,抛出泛型NullPointerException是一个非常糟糕的选择,因为它本身并不表示违反合同。IllegalArgumentException会是更好的选择。



Sidenote:
I don't know whether Java allows this (I doubt it), but C/C++ programmers use an assert()in this case, which is significantly better for debugging: It tells the program to crash immediately and as hard as possible should the provided condition evaluate to false. So, if you ran

旁注:
我不知道 Java 是否允许这样做(我对此表示怀疑),但是 C/C++ 程序员assert()在这种情况下使用 an ,这对于调试来说明显更好:它告诉程序在提供的情况下立即崩溃并尽可能严重条件评估为假。所以,如果你跑了

void MyClass_foo(MyClass* me, int (*someFunction)(int)) {
    assert(me);
    assert(someFunction);

    ...
}

under a debugger, and something passed NULLinto either argument, the program would stop right at the line telling which argument was NULL, and you would be able to examine all local variables of the entire call stack at leisure.

在调试器下,将某些内容传递NULL给任一参数,程序将在告诉哪个参数是 的行处停止NULL,您将能够闲暇时检查整个调用堆栈的所有局部变量。

回答by Arenim

It's because it's possible for it notto happen naturally. Let's see piece of code like this:

这是因为它是可能它不是自然发生的。让我们看看这样的一段代码:

bool isUserAMoron(User user) {
    Connection c = UnstableDatabase.getConnection();
    if (user.name == "Moron") { 
      // In this case we don't need to connect to DB
      return true;
    } else {
      return c.makeMoronishCheck(user.id);
    }
}

(of course there is numerous problems in this sample about code quality. Sorry to lazy to imagine perfect sample)

(当然这个示例代码质量有很多问题。抱歉懒得想出完美的示例)

Situation when cwill not be actually used and NullPointerExceptionwill not be thrown even if c == nullis possible.

情况时,c将不被实际使用,并NullPointerException即使不被拆毁c == null是可能的。

In more complicated situations it's becomes very non-easy to hunt down such cases. This is why general check like if (c == null) throw new NullPointerException()is better.

在更复杂的情况下,追查此类案件变得非常不容易。这就是为什么一般检查就好了if (c == null) throw new NullPointerException()

回答by Fairoz

It is intentional to protect further damage, or to getting into inconsistent state.

有意保护进一步的损害,或进入不一致的状态。

回答by Erk

Apart from all other excellent answers here, I'd also like to add a few cases.

除了这里的所有其他优秀答案外,我还想添加一些案例。

You can add a message if you create your own exception

如果您创建自己的例外,则可以添加一条消息

If you throw your own NullPointerExceptionyou can add a message (which you definitely should!)

如果你扔自己的,NullPointerException你可以添加一条消息(你绝对应该这样做!)

The default message is a nullfrom new NullPointerException()and all methods that use it, for instance Objects.requireNonNull. If you print that null it can even translate to an empty string...

默认消息是nullfromnew NullPointerException()和使用它的所有方法,例如Objects.requireNonNull。如果你打印那个 null 它甚至可以转换成一个空字符串......

A bit short and uninformative...

有点短而且信息量不足......

The stack trace will give a lot of information, but for the user to know what was null they have to dig up the code and look at the exact row.

堆栈跟踪将提供大量信息,但为了让用户知道什么是 null,他们必须挖掘代码并查看确切的行。

Now imagine that NPE being wrapped and sent over the net, e.g. as a message in a web service error, perhaps between different departments or even organizations. Worst case scenario, no one may figure out what nullstands for...

现在想象 NPE 被包装并通过网络发送,例如作为 Web 服务错误中的消息,可能在不同部门甚至组织之间。最坏的情况,没有人可能弄清楚什么null代表......

Chained method calls will keep you guessing

链式方法调用会让你猜不透

An exception will only tell you on what row the exception occurred. Consider the following row:

异常只会告诉您异常发生在哪一行。考虑以下行:

repository.getService(someObject.someMethod());

If you get an NPE and it points at this row, which one of repositoryand someObjectwas null?

如果您得到一个 NPE 并且它指向这一行,那么repositorysomeObject中的哪一个是空的?

Instead, checking these variables when you get them will at least point to a row where they are hopefully the only variable being handled. And, as mentioned before, even better if your error message contains the name of the variable or similar.

相反,当你得到这些变量时检查它们至少会指向一行,在那里它们有望成为唯一被处理的变量。而且,如前所述,如果您的错误消息包含变量名称或类似名称,那就更好了。

Errors when processing lots of input should give identifying information

处理大量输入时的错误应提供识别信息

Imagine that your program is processing an input file with thousands of rows and suddenly there's a NullPointerException. You look at the place and realize some input was incorrect... what input? You'll need more information about the row number, perhaps the column or even the whole row text to understand what row in that file needs fixing.

想象一下,您的程序正在处理一个包含数千行的输入文件,突然出现 NullPointerException。你看着那个地方,意识到一些输入是不正确的......什么输入?您将需要有关行号的更多信息,可能是列甚至整行文本,以了解该文件中的哪一行需要修复。