Java:检查空值或允许异常处理

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3223730/
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-10-30 00:52:10  来源:igfitidea点击:

Java: check for null or allow exception handling

java

提问by AJay

I'm wondering about the cost of using a try/exception to handle nulls compared to using an if statement to check for nulls first.

我想知道与使用 if 语句首先检查空值相比,使用 try/exception 处理空值的成本。

To provide more information. There's a > 50% chance of getting nulls, because in this app. it is common to have a null if no data has been entered... so to attempt a calculation using a null is commonplace.

提供更多信息。有 > 50% 的机会获得空值,因为在这个应用程序中。如果没有输入数据,通常会有一个空值……所以尝试使用空值进行计算是司空见惯的。

This being said, would it improve performance if I use an if statement to check for null first before calculation and just not attempt the calculation in the first place, or is less expensive to just let the exception be thrown and handle it?

话虽如此,如果我在计算之前首先使用 if 语句检查 null 并且不首先尝试计算,或者只是让异常被抛出并处理它的成本更低,它会提高性能吗?

thanks for any suggestions :-)

感谢您的任何建议:-)

Thanks for great thought provoking feedback! Here's a PSEUDOcode example to clarify the original question:

感谢发人深省的反馈!这是一个 PSEUDOcode 示例来澄清原始问题:

BigDecimal value1 = null //assume value1 came from DB as null
BigDecimal divisor = new BigDecimal("2.0");

try{
    if(value1 != null){ //does this enhance performance?... >50% chance that value1 WILL be null
        value1.divide(divisor);
    }
}
catch (Exception e){
    //process, log etc. the exception
    //do this EVERYTIME I get null... or use an if statement
    //to capture other exceptions.
}

回答by duffymo

I'd recommend checking for null and not doing the calculation rather than throwing an exception.

我建议检查 null 并且不进行计算而不是抛出异常。

An exception should be "exceptional" and rare, not a way to manage flow of control.

异常应该是“异常的”和罕见的,而不是一种管理控制流的方法。

I'd also suggest that you establish a contract with your clients regarding input parameters. If nulls are allowed spell it out; if they're not, make it clear what should be passed, default values, and what you promise to return if a null value is passed.

我还建议您与客户就输入参数签订合同。如果允许空值,则将其拼写出来;如果不是,请说明应该传递什么、默认值以及在传递空值时您承诺返回的内容。

回答by BalusC

If passing nullargument is an exceptional case, then I'd throw a NullPointerException.

如果传递null参数是一个特例,那么我会抛出一个NullPointerException.

public Result calculate(Input input) {
    if (input == null) throw new NullPointerException("input");
    // ...
}

If passing nullis an allowed case, then I'd skip the calculation and eventually return null. But that makes in my opinion less sense. Passing nullin first instance would seem a bug in the calling code.

如果传递null允许的情况,那么我将跳过计算并最终返回null。但这在我看来不太有意义。传入null第一个实例似乎是调用代码中的一个错误。

Whatever way you choose, it should be properly documented in the Javadoc to avoid surprises for the API user.

无论您选择哪种方式,都应在 Javadoc 中正确记录以避免给 API 用户带来意外。

回答by TofuBeer

Try and catch are close to "free" but throws can be very expensive. Typically VM creators do not optimize exception paths since, well, they are exceptional (supposed to be rare).

尝试和捕捉接近于“免费”,但抛出可能非常昂贵。通常 VM 创建者不会优化异常路径,因为它们是异常的(应该很少见)。

NullPointerException indicates a programmer mistake (RuntimeException) and should not be caught. Instead of catching the NullPointerException you should fix your code to cause the exception not to be thrown in the first place.

NullPointerException 表示程序员错误 (RuntimeException),不应被捕获。与其捕获 NullPointerException,不如修复您的代码,以免首先抛出异常。

Worse yet, if you catch the NullPointerException and a different part of the calculation code throws NullPointerException than the one you expect to throw it you have now masked a bug.

更糟糕的是,如果您捕获 NullPointerException 并且计算代码的不同部分抛出 NullPointerException 而不是您期望抛出的部分,那么您现在已经掩盖了一个错误。

To fully answer your question, I would implement it one way, profile it, and then implement it the other way and profile it... then I would only use the one with the if statement that avoids throwing the NullPointerException regardless of which is faster simply because of the point above.

为了完全回答您的问题,我会以一种方式实现它,对其进行分析,然后以另一种方式实施并对其进行分析...然后我将只使用带有 if 语句的一种,该语句避免抛出 NullPointerException,无论哪种方式更快仅仅因为上面的一点。

回答by developmentalinsanity

If there's a >50% chance of getting a null, then it's hardly an exception?

如果有> 50% 的机会获得空值,那么这几乎不是一个例外?

Personally, I'd say that if you expect something to happen, you should code for it appropriately - in this case, checking for null and doing whatever is appropriate. I've always understood throwing an exception to not be exceedingly cheap, but couldn't say for certain.

就我个人而言,我会说,如果您希望发生某些事情,您应该适当地对其进行编码 - 在这种情况下,检查 null 并做任何适当的事情。我一直理解抛出异常并不是非常便宜,但不能肯定。

回答by Don Roby

I agree with most of the other responses that you should prefer the null check to the try-catch. And I've upvoted some of them.

我同意大多数其他答复,即您应该更喜欢空检查而不是 try-catch。我已经对其中一些进行了投票。

But you should try to avoid the need as much as possible.

但是你应该尽量避免这种需要。

There's a > 50% chance of getting nulls, because in this app. it is common to have a null if no data has been entered... so to attempt a calculation using a null is commonplace.

有 > 50% 的机会获得空值,因为在这个应用程序中。如果没有输入数据,通常会有一个空值……所以尝试使用空值进行计算是司空见惯的。

That's what you should really be fixing.

这才是你真正应该解决的问题。

Come up with sensible default values that ensure the computation works or avoid calling a computation without supplying the needed input data.

提出合理的默认值,以确保计算工作或避免在不提供所需输入数据的情况下调用计算。

For many of the standard data types and computations involving them there are sensible default values. Default numbers to 0 or 1 depending on their meaning, default strings and collections to empty, and many computations just work. For more complex objects of your own making, consider the Null Objectpattern.

对于许多涉及它们的标准数据类型和计算,都有合理的默认值。默认数字为 0 或 1,具体取决于它们的含义,默认字符串和集合为空,并且许多计算都有效。对于您自己制作的更复杂的对象,请考虑空对象模式。

回答by jgmjgm

If you have any case where a result or input can't be handled by you program then that should be an error. You should know what you program can handle and allow only that. In regards to possible future cases where a result could be handled but isn't yet I would still suggest considering it an error until you actually need that result. If in doubt, you don't know, the program doesn't know, it can't be handled, you haven't equipped your program to handle it so it's an error. The program can't do anything more but to stop.

如果您遇到任何程序无法处理结果或输入的情况,那么这应该是一个错误。您应该知道您的程序可以处理什么并且只允许这样做。关于可以处理结果但尚未处理的未来可能情况,我仍然建议将其视为错误,直到您真正需要该结果为止。如果有疑问,你不知道,程序不知道,它不能被处理,你没有装备你的程序来处理它,所以这是一个错误。该程序只能停止。

For user input it is a very bad idea to rely on your program to eventually crash. You don't know when or even if it will crash. It might just end up doing the wrong thing or do ten things then crash that it shouldn't have done and wouldn't have done if input had been validated.

对于用户输入,依赖您的程序最终崩溃是一个非常糟糕的主意。你不知道它何时甚至是否会崩溃。它可能最终会做错事或做十件事,然后崩溃,如果输入已经过验证,它不应该做并且不会做。

In terms of guarding against your own mistakes that is more of a mixed bag. You'll want to focus a lot more on making sure things work by testing, eliminating unknowns, proof reading, making sure you know exactly how your program works, etc. Still you'll occasionally have cases where internal processing might produce undesirable results.

在防范自己的错误方面,这更像是一个混合包。您将需要更多地关注通过测试、消除未知数、校对、确保您确切地知道程序的工作原理等来确保事情正常工作。但您仍然偶尔会遇到内部处理可能会产生不良结果的情况。

When it turns out a result isn't an error for a given case you do not handle the exception, you handle null, not an exception. In that case the result is not an error. So you handle the result and not the error. It could not be simpler. If you're catching an exception and then doing something that can be done with an if such as:

当结果不是给定情况的错误时,您不处理异常,而是处理空值,而不是异常。在这种情况下,结果不是错误。所以你处理结果而不是错误。再简单不过了。如果您捕获异常然后执行可以使用 if 完成的操作,例如:

try
    extra = i_need_it(extra_id)
    show('extra', extra)
catch
    show('add_extra')

Then that is not right. You have a perfectly acceptable course of action if you don't have the extra thing.

那么这是不对的。如果你没有多余的东西,你就有一个完全可以接受的行动方案。

This is much better and it keeps your intention clear without the extra verbosity:

这要好得多,它可以让您的意图清晰而没有多余的冗长:

Something extra = i_want_it(extra_id)
if extra ==== null 
    show('add_extra')
else
    show('extra', extra)

Notice here you need nothing special to avoid catching an exception from another layer. How I put try catch above is a bad practice. You should only be wrapping the thing that throws an exception:

注意这里你不需要什么特别的东西来避免从另一个层捕获异常。我如何将 try catch 放在上面是一种不好的做法。你应该只包装抛出异常的东西:

Something extra
try
    extra = i_need_it(extra_id)
if extra === null
    show('add_extra')
else
    show('extra', extra)

When you thing about it like that then it is just converting null to exception and then back again. This is Yo-Yo coding.

当你像那样处理它时,它只是将 null 转换为异常,然后再返回。这是悠悠球编码。

You should start with:

你应该从:

Object i_need_it(int id) throws

Until you are actually able to implement handling for null. If you're able to implement handling for the exception you can implement the handling for the null.

直到您真正能够实现对 null 的处理。如果您能够实现对异常的处理,则可以实现对 null 的处理。

When it turns out that something isn't always needed either add this method or change i_need_it to it (if null is always handled):

当事实证明并不总是需要某些东西时,请添加此方法或将 i_need_it 更改为它(如果始终处理 null):

Object|null i_want_it(int id)

An alternative is to check is it exists first:

另一种方法是先检查它是否存在:

bool does_it_exist(int id)

The reason this isn't done so often is because it usually comes out like this:

这不是经常这样做的原因是因为它通常是这样的:

if(does_it_exist(id))
    Something i_need = i_need_it(id)

This tends to be more prone to concurrency problems, can require more calls that might be unreliable (IO over network) and can be inefficient (two RTTs rather than one). Other calls are often merged like this such as update if exists, insert if unique, update if exists or insert, etc that then return what would normally be the result of instead initially checking. Some of these have conflicts over payload size efficiency and RTT efficiency which can also vary based on a number of factors.

这往往更容易出现并发问题,可能需要更多可能不可靠的调用(网络上的 IO)并且效率低下(两个 RTT 而不是一个)。其他调用通常像这样合并,例如如果存在则更新,如果唯一则插入,如果存在则更新或插入等,然后返回通常是最初检查的结果。其中一些在有效载荷大小效率和 RTT 效率方面存在冲突,这也可能因许多因素而异。

It is cheaper however when you need alternating behaviour based on if something exists or not but you don't need to work on it. If you also don't need to worry about the above concerns it's a bit clearer.

然而,当您需要基于某物是否存在而交替行为但您不需要对其进行处理时,它会更便宜。如果您也不需要担心上述问题,那就更清楚了。

You may even want:

你甚至可能想要:

void it_must_exist(int id) throws

This is again useful because if you need only ensure something exists it's often cheaper than getting it. However it's rare you'll need this as in most cases you'll want to know if something exists so to directly work on it.

这再次很有用,因为如果您只需要确保某些东西存在,它通常比获得它便宜。但是,您很少需要它,因为在大多数情况下,您会想知道是否存在某些东西以便直接对其进行处理。

A way to conceive it is that you wouldn't make 'does_it_exist' throw an exception when it can simply return a boolean explicitly up the call stack, 'i_want_it' is a combined 'has' and 'get' in effect.

一种设想它的方法是,当 'does_it_exist' 可以简单地在调用堆栈上显式返回一个布尔值时,您不会让 'does_it_exist' 抛出异常,'i_want_it' 是有效的组合 'has' 和 'get'。

While having two separate methods more clearly separates method signatures, sometimes you may need to pass down from something else and the simplest way for that if you don't mine a bit of ambiguity is:

虽然有两个单独的方法可以更清楚地将方法签名分开,但有时您可能需要从其他东西传递下去,如果您没有发现一点歧义,最简单的方法是:

Object|null get(int id, bool require) throws

This is better as you're handing the contract down the call chain rather than building on a house of sand based on action at a distance. There are ways to pass down your contract more explicitly but it tends to be convoluted YAGNI (IE, pass down a method caller).

这更好,因为您将合同传递给调用链,而不是根据远距离的动作在沙屋上建造。有很多方法可以更明确地传递你的合同,但它往往是复杂的 YAGNI(即传递一个方法调用者)。

You should throw exceptions early and you can want to be safe rather than sorry so false positives are fine. Once you discover it's a false positive though then you fix it at the source.

您应该尽早抛出异常,并且您可能希望安全而不是抱歉,因此误报是可以的。一旦你发现它是一个误报,那么你就在源头上修复它。

It should be extremely rare that you're handling exceptions at all. The sheer majority should hit the top, then invoke a logging and output handler. The rest you fix appropriately by passing back the result directly and handling it. When you have one false positive out of many uses, only this that use. Don't just remove the check in the root and break the many other cases where it's still an error.

您完全处理异常应该是非常罕见的。绝对多数应该到达顶部,然后调用日志记录和输出处理程序。其余的你通过直接传回结果并处理它来适当地修复。当您在许多用途中只有一个误报时,只有这个用途。不要只是删除根目录中的检查并破坏它仍然是错误的许多其他情况。

Java is an unfortunate language because I you can't have a way of saying don't pass null or this variable must be non-null.

Java 是一种不幸的语言,因为我无法说不传递 null 或此变量必须为非 null。

When such a feature is lacking, It's often best to check for nulls at their sources, things such as IO rather than for every time something is passed to one of your methods. Otherwise that's an absurd amount of null checking.

当缺少这样的功能时,通常最好检查它们的来源是否为空值,例如 IO,而不是每次将某些内容传递给您的方法之一时。否则,这是荒谬的空检查。

You can apply this pattern to create functions to replace your ifs for parameter checking if you really need that. You would replace id with the object itself such as:

如果你真的需要,你可以应用这个模式来创建函数来替换你的 ifs 来检查参数。您可以将 id 替换为对象本身,例如:

Object i_want(Object it) throws
    if(it === null)
        throw
    return it;

Then:

然后:

void give_me(Object it)
    this.it = Lib<Object>::i_want(it)

A shame there's no passthru type.

可惜没有通路类型。

Or:

或者:

void i_get_it(Getter<Object> g)
    this.it = Lib<Object>::i_want(g.gimme())

You might have a specific Getter rather than with generic.

您可能有一个特定的 Getter 而不是通用的。

Or:

或者:

void i_need_it(Result<Object> r)
    this.it = r.require()

If you only do it on the boundary instead (when you call things out side of your control where non-null result can't be guaranteed), while it is preferred to do it there or for any usage of a method documented as returning null and only there as that's where it's really only needed, that does mean that when you do get a null where it doesn't belong (mistakes happen) you're not going to have an easy time finding out where it came from. It can get passed around a lot from IO and not produce a null pointer exception until something tries to operate on it. Such nulls can be passed around the system for days or even months as a ticking time bomb waiting to go off.

如果您只在边界上执行此操作(当您在无法保证非空结果的情况下在控制范围之外调用事物时),而最好在那里执行此操作或记录为返回 null 的任何方法的使用并且只有在那里才真正需要它,这确实意味着当您确实在不属于它的地方(发生错误)获得空值时,您将不会很容易找出它的来源。它可以从 IO 中传递很多信息,并且在尝试对其进行操作之前不会产生空指针异常。这样的空值可以在系统中传递数天甚至数月,就像等待爆炸的定时炸弹一样。

I wouldn't do it myself but I wouldn't blame people in some cases for implementing the defensive programming approach above which might be required due to Java's broken type system which is loose by default and can't be restricted. In robustly typed languages, null isn't permitted unless you explicitly say it is.

我自己不会这样做,但在某些情况下,我不会责怪人们实施上述防御性编程方法,由于 Java 的类型系统损坏,默认情况下该系统是松散的且无法限制,因此可能需要这种方法。在强健类型语言中,除非您明确说明,否则不允许使用 null。

Please be advised that although I call it broken, I have been typically using significantly looser languages heavily for decades to build large, complex and critical systems without having to litter the codebase with superfluous checks. Discipline and competence are what determine quality.

请注意,虽然我称它为坏的,但几十年来我通常一直大量使用松散的语言来构建大型、复杂和关键的系统,而不必用多余的检查来乱扔代码库。纪律和能力决定了质量。

A false positive is when a result or a condition occurs that you assume is a result that can't be handled by all callers but it turns out that at least one caller can handle it appropriately. In that case you don't handle the exception but instead give the caller the result. There are very few exceptions to this.

误报是指当您假设结果或条件无法由所有调用者处理的结果时,但事实证明至少有一个调用者可以适当地处理它。在这种情况下,您不处理异常而是将结果提供给调用者。很少有例外。

Java 8 has Optional but it doesn't really look helpful. It's a horrific case of the inner platform effect trying to implement new syntax as classes and ending up having to add half of the existing Java syntax along with it making it very clunky. As usual modern Java OOP, solves every problem of people wanting less fudge by adding more fudge and over complicating things. If you really want chaining like that you might want to try something such as kotlin which implements that syntax directly.

Java 8 有 Optional 但它看起来并没有真正的帮助。这是内部平台效应的一个可怕案例,它试图将新语法实现为类,最终不得不添加一半的现有 Java 语法,使其变得非常笨拙。像往常一样,现代 Java OOP 通过添加更多的软糖和使事情变得过于复杂来解决人们想要更少软糖的所有问题。如果您真的想要这样的链接,您可能想尝试直接实现该语法的 kotlin 之类的东西。

A modern language will mean you don't have to worry about most of this and just have:

现代语言意味着您不必担心大部分内容,只需:

void i_need_it(Object it)
    this.it = it

void i_want_it(Object? it)
    this.it = it

A modern language might even return the original object for a method without a return (replace void with self as the standard and auto-return) so people can have their fancy chaining or whatever else is fashionable these days in programming.

现代语言甚至可能会在没有返回的情况下为方法返回原始对象(用 self 替换 void 作为标准和自动返回),因此人们可以拥有自己喜欢的链接或当今编程中流行的任何其他东西。

You can't have a factory with a base class that gives you a Null or NotNull either because you'll still have to pass the base class and that'll be a type violation when you say you want NotNull.

你不能有一个带有基类的工厂,它给你一个 Null 或 NotNull,因为你仍然必须传递基类,当你说你想要 NotNull 时,这将是类型冲突。

You might want to play around with aspects, static analysis, etc although that's a bit of a rabbit hole. All documentation should indicate if null can be returned (although if indirectly the it can potentially be left out).

您可能想尝试方面、静态分析等,尽管这有点像兔子洞。所有文档都应指明是否可以返回 null(尽管如果间接,它可能会被排除在外)。

You can make a class such as MightHave to wrap your result where you can put on methods like get, require and has if you don't like statics but want to eat an if although it's also in the realm of mildly convoluted and messing with all of your method signatures everywhere boxing everything all over the place, an ugly solution. It's only really handy as an alternative to those rare exception handling cases where exceptions do seem useful due to the complexity of the call graph and the number of unknowns present across layers.

您可以创建一个诸如 MightHave 之类的类来包装您的结果,如果您不喜欢静态但想要吃一个 if,您可以在其中使用诸如 get、require 和 has 之类的方法,尽管它也处于轻度复杂和混乱的领域你的方法签名无处不在,把所有东西都装箱了,这是一个丑陋的解决方案。它只是作为那些罕见的异常处理情况的替代方案非常方便,在这些情况下,由于调用图的复杂性和跨层存在的未知数,异常似乎很有用。

One exceptionally rare case is when your source knows what exception to throw but not if it needs to be thrown but it's hard to pass down (although coupling two layers at a distance where anything can happen in between needs to be approached with caution). Some people might also want this because it can easily give a trace of both where the missing item came from and where it was required which is something using checks might not give (they're likely to fail close to the source but not guaranteed). Caution should be taken as those kinds of problems might be more indicative of poor flow control or excessive complexity than an issue with justified polymorphism, meta/dynamic coding and the like.

一种极为罕见的情况是,您的源知道要抛出什么异常,但不知道是否需要抛出异常,但很难传递下去(尽管需要谨慎处理相距一定距离的两层,在此距离之间可能发生任何事情)。有些人可能也想要这个,因为它可以很容易地跟踪丢失物品的来源和需要它的地方,这是使用检查可能无法提供的东西(他们可能会在靠近源的地方失败,但不能保证)。应该小心,因为与合理的多态性、元/动态编码等问题相比,这些类型的问题可能更表明流程控制不佳或过于复杂。

Caution should be taken with things such as defaults or the Null Object pattern. Both can end up hiding errors and becoming best guesses or a cure worse than the disease. Things such a NullObject pattern and Optional can often be used to simply turn off or rather bi-pass the inbuilt error handling in your interpreter.

应该注意诸如默认值或空对象模式之类的事情。两者都可能最终隐藏错误并成为最佳猜测或比疾病更糟糕的治疗方法。诸如 NullObject 模式和 Optional 之类的东西通常可以用来简单地关闭或更确切地说是解释器中内置的错误处理。

Defaults aren't always bad but can fall into the realm of guessing. Hiding errors from the user end up setting them up to fail. Defaults (and sanitisation) always need to be thought out carefully.

默认值并不总是很糟糕,但可能会陷入猜测的境地。对用户隐藏错误最终会使它们失败。总是需要仔细考虑默认值(和消毒)。

Things such as NullObject pattern and Optional can be overused to effectively turn off null pointer checking. It simply makes the assumption that null is never an error which ends up with programs doing somethings, not others but you know not what. In some cases this might have hilarious results such as if the user's credit card is null. Make sure if you're using such things you're not using them to the effect of simply wrapping all of your code in a try catch that ignores a null pointer exception. This is very common because people tend to fix errors where the exception was thrown. In reality the real error tends to be further away. You end up with one piece of faulty IO handling that erroneously returns null and that null gets passed all around the program. Instead of fixing that one source of null, people will instead try to fix it in all the places it reaches where it causes an error.

诸如 NullObject 模式和 Optional 之类的东西可以被过度使用来有效地关闭空指针检查。它只是假设 null 永远不会是一个错误,它最终会导致程序做某事,而不是其他人,但您不知道是什么。在某些情况下,这可能会产生有趣的结果,例如用户的信用卡是否为空。确保如果您正在使用这些东西,您不会使用它们来简单地将所有代码包装在忽略空指针异常的 try catch 中。这很常见,因为人们倾向于修复抛出异常的错误。实际上,真正的错误往往离得更远。你最终会得到一个错误的 IO 处理,它错误地返回 null 并且 null 被传递到整个程序。而不是修复那个空值的来源,

NullObject pattern or MagicNoop patterns have their place but are not for common use. You shouldn't use them until it becomes immediately apparent they are be useful in a justified manner that isn't going to cause more problems than it solves. Sometimes a noop is effectively an error.

NullObject 模式或 MagicNoop 模式有其一席之地,但并不常用。你不应该使用它们,直到它们立即变得明显,它们以合理的方式有用,不会导致比它解决的问题更多的问题。有时,noop 实际上是一个错误。