2010-07-11 38 views
8

我想知道使用try/exception来处理空值与使用if语句首先检查空值相比的成本。Java:检查null或允许异常处理

提供更多信息。获得空值的可能性> 50%,因为在此应用中。如果没有输入数据,那么通常会出现null ...所以尝试使用空值进行计算是司空见惯。

这就是说,它会提高性能,如果我使用if语句在计算之前先检查null,而不是首先尝试计算,或者只是抛出异常并处理它?

感谢您的任何建议:-)

感谢伟大的发人深省的反馈!下面是一个伪代码例子来阐明原题:

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. 
} 
+2

ack ...不要捕捉异常...如果除数为零,该怎么办?然后,您还将捕获除以零的ArithmeticException,但将其视为“预期的”NullPointerException。尽可能具体说明你想要捕捉的异常情况。 – TofuBeer 2010-07-11 21:27:45

+0

有没有人想把自己的脖子伸出来,并说if语句比抛出异常要便宜? – AJay 2010-07-12 00:50:32

+0

是的,我喜欢。 'if'语句比抛出异常要便宜得多。 – EJP 2010-07-12 06:37:05

回答

11

我建议你检查空,而不是做计算,而不是抛出异常。

一个例外应该是“特殊”且罕见的,而不是管理控制流的方式。

我还建议您与客户就输入参数建立合同。如果允许空位拼出来,如果它们不是,请清楚应该传递什么,默认值,以及如果传递空值时您承诺返回的内容。

+0

绝对是真的 – odiszapc 2013-09-18 10:22:57

3

如果有> 50%的机会得到一个null,那么它几乎不是一个例外?

就个人而言,我会说,如果你希望一些事情发生,你应该为它适当的代码 - 在这种情况下,检查空和做什么是适当的。我总是理解抛出一个例外,不是非常便宜,但不能肯定地说。

7

如果通过null参数为例外情况,那么我会抛出一个NullPointerException

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

如果通过null允许的情况下,那么我会跳过计算,最终又返回null。但这在我看来意义不大。首先传递null似乎是调用代码中的一个错误。

无论您选择什么方式,都应该在Javadoc中正确记录以避免API用户感到意外。

+1

在这种情况下,如果您没有记录“输入”被提供为空是可以接受的,那么我会抛出'IllegalArgumentException'而不是'NullPointerException'。我将'NullPointerException'解释为类似于分段错误:主机运行时检测为编程错误,而不是应用程序本身使用的东西。 根据我自己的建议,JDK文档读取:“应用程序应该抛出此类的实例以指示其他非法使用null对象。” http://java.sun.com/javase/7/docs/api/java/lang/NullPointerException.html – seh 2010-07-11 16:51:18

+1

@seh:_Effective Java 2nd Edition_声明'NullPointerException'是非法'null'参数的约定。 – polygenelubricants 2010-07-11 17:02:03

+2

@seh:'IllegalArgumentException'只适用于不为'null'的非法参数。编辑:@poly说,是的:)在Java SE/EE API中窥视,你会发现这个约定也适用于那里。 – BalusC 2010-07-11 17:03:06

3

尝试和赶上接近“免费”,但投掷可能非常昂贵。通常,VM创建者不会优化异常路径,因为它们是例外的(应该很少见)。

NullPointerException指示程序员错误(RuntimeException)并且不应被捕获。而不是捕获NullPointerException,你应该修复你的代码,以使异常不被抛出。

更糟的是,如果您发现NullPointerException,并且计算代码的不同部分会抛出NullPointerException,那么您现在已经屏蔽了一个错误。

为了完全回答你的问题,我会实现它的一种方式,配置文件,然后以另一种方式实现它,然后配置文件...然后我只会使用带有if语句的函数来避免抛出NullPointerException,而不管因为上面的观点,速度更快。

0

我同意大多数其他答案,您应该更喜欢try-catch的空检查。我已经提高了其中的一些。

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

获得空值的概率大于50%,因为在这个应用程序中。如果没有输入数据,那么通常会出现null ...所以尝试使用空值进行计算是司空见惯。

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

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

对于涉及它们的许多标准数据类型和计算,都有明智的默认值。默认数字为0或1,具体取决于它们的含义,缺省字符串和集合为空,许多计算正常。对于您自己制作的更复杂的物体,请考虑Null Object模式。

+0

默认值本身最终可能是隐藏错误的情况。这不是没有限制,但默认情况下,你仍然更好地抛出异常。回退需要很少应用,并且要根据具体情况应用,只有在真正有意义且理想的用户清楚的地方,例如yes/no,default yes :)。默认值应该比纠正错误更方便。 – jgmjgm 2018-02-28 15:55:05

0

如果您有任何结果或输入无法由您的程序处理的情况下,那应该是一个错误。你应该知道你的程序可以处理和只允许。对于未来可能出现的情况,如果结果可以处理,但还没有结果,我仍然建议将其视为一个错误,直到您真正需要结果为止。如果有疑问,你不知道,程序不知道,它不能处理,你没有配备你的程序来处理它,所以这是一个错误。该计划不能做任何事情,只能停下来。

对于用户输入来说,依靠程序最终崩溃是一个非常糟糕的主意。你不知道什么时候,甚至会不会崩溃。它可能只是最终做错了事,或者做了十件事,然后崩溃,它不应该做,并且如果输入已被验证,则不会做。

在防范自己的错误方面,更多的是混合包。您需要更多地关注通过测试来确保工作正常,消除未知因素,校对阅读,确保您确切知道程序的工作方式等。您偶尔还会遇到内部处理可能产生不良结果的情况。

事实证明,对于给定的情况,结果不是错误,你不处理异常,你处理null,而不是一个例外。在这种情况下,结果不是错误。所以你处理结果而不是错误。这不可能更简单。如果你正在捕捉一个异常,然后做一些可以做的事情,如果这样做,如:

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

然后,这是不对的。如果你没有额外的东西,你有一个完全可以接受的行动方案。

这是好多了,它让你的意图明显没有额外的冗长:这里

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

通知你需要什么特别的避免受凉从另一层异常。我如何把上面的尝试抓住是一个不好的做法。你应该只是包装引发异常的东西:

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

当你这样的事情,那么它只是将null转换为异常,然后再回来。这是Yo-Yo编码。直到你实际上是能够实现处理的空

Object i_need_it(int id) throws 

你应该开始。如果您能够实现对异常的处理,则可以实现对null的处理。

当事实证明,事情并不总是需要添加任何此方法或更改i_need_it它(如果为null总是处理):

Object|null i_want_it(int id) 

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

bool does_it_exist(int id) 

不这样做,所以经常的原因是因为它通常出来是这样的:

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

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

然而,当需要根据存在与否存在交替行为时更便宜,但是您不需要处理它。如果你也不需要担心上述问题,它会更清晰一些。

你可能甚至想:

void it_must_exist(int id) throws 

这又是有用的,因为如果你只需要确保的东西存在,它往往比得到更便宜。然而,很少有人会需要这样做,因为在大多数情况下,您会想知道是否存在某些内容以便直接处理它。

构想它的一种方式是,如果'does_it_exist'只是简单地在调用堆栈上显式返回一个布尔值,那么'i_want_it'是组合'has'和'get'的效果。

虽然有两种不同的方法更加明确分离方法签名,有时你可能需要从别的东西和最简单的方式传递下来,如果你不是我的位模糊的是:

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

这是更好的,因为你将合同交给呼叫链,而不是建立在远离行动的沙滩上。有许多方法可以更明确地传递你的契约,但它往往会让人费解YAGNI(IE,传递一个方法调用者)。

你应该提前抛出异常,你可以想要安全而不是遗憾,所以误报是好的。一旦你发现它是一个误报,尽管如此,你可以在源头修复它。

你应该极其罕见地处理异常。绝大多数应该击中顶部,然后调用日志记录和输出处理程序。通过直接传递结果并处理它,您可以适当地修复其他问题。当你有许多用途中有一个误报时,只有这个用法。不要只是删除根目录中的检查,并打破许多其他仍然存在错误的情况。

Java是一种不幸的语言,因为我不能有一种说法不传递null的方法,或者这个变量必须是非空的。

当缺少这样的功能时,通常最好在它们的来源(例如IO)中检查空值,而不是每次将某个东西传递给某个方法时使用。否则,这是一个荒谬的空值检查。

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

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

然后:

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

一种耻辱没有中继类型。

或者:

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

你可能有一个特定的吸气剂,而不是通用的。

或者:

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

如果你只能做它的边界而不是在(当你打电话东西拿出来控制,其中非空的结果不能得到保证的一侧),而最好是做它在那里或任何用法的方法被记录为返回null并且只在那里,因为这是真正需要的地方,这就意味着当你得到一个null不属于它的地方时(错误发生),你不会去有一个容易的时间找出它来自哪里。它可以从IO传来,并且不会产生空指针异常,除非有东西试图对其进行操作。这些空值可以在系统中传递几天甚至几个月,作为等待中止的定时炸弹。

我不会自己这样做,但在某些情况下,我不会责怪人们实施上面的防御性编程方法,因为Java的破坏类型系统默认情况下是松散的并且不能被限制。在强类型语言中,除非明确声明,否则不允许使用null。

请注意,尽管我称之为破坏,但几十年来我一直在使用明显较宽松的语言来构建大型,复杂和关键系统,而不必花费过多的检查来代码。纪律和能力决定了质量。

假结果或情况发生时,您认为是所有调用者都无法处理的结果,但至少有一个调用者可以正确处理它。在这种情况下,你不会处理异常,而是给调用者提供结果。这很少有例外。

Java 8具有可选功能,但看起来并没有什么帮助。这是内部平台效应的一个可怕的例子,它试图将新的语法作为类实现,并且最终不得不添加一半现有的Java语法,使得它非常笨重。像往常一样,现代Java OOP通过增加更多功能和更复杂的东西解决了每个人都希望减少欺骗的问题。如果你真的想像那样链接,你可能想尝试一些例如直接实现该语法的kotlin。

现代语言将意味着你不必担心极本,只是有:

void i_need_it(Object it) 
    this.it = it 

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

现代语言甚至可能回到原来的对象的方法,而不返回(替换为空自我作为标准和自动返回),所以人们可以在编程中拥有自己的花哨链接或其他任何时髦的东西。

你不能有一个基类的工厂给你一个Null或NotNull,因为你仍然需要传递基类,并且当你说你想要NotNull的时候这将是一个类型违规。

你可能想玩弄方面,静态分析等,虽然这有点兔子漏洞。所有的文档都应该指出是否可以返回null(尽管如果是间接的,它可能会被忽略)。

你可以让一个类如MightHave包裹你的结果,你可以把它放在get,require等方法上,如果你不喜欢静态但是想吃一个if虽然它也处于轻度复杂的领域并将所有方法签名搞乱,将遍布各处的所有东西都捆绑起来,这是一个丑陋的解决方案。作为替代罕见异常处理的例子,由于调用图的复杂性和各层中未知数的数量,异常看起来很有用,它只是非常方便。

一个极其罕见的情况是,当你的源代码知道要抛出什么异常时,如果它需要抛出但不能传递下去(尽管在两个层之间需要接近警告)。有些人可能也想要这样做,因为它可以轻松地追踪丢失项目来自哪里以及需要哪些项目,这些都是使用检查可能无法提供的(它们很可能在靠近源的位置失败,但不能保证)。应该谨慎,因为这些问题可能比合理多态性,元/动态编码等问题更能表明流控制不佳或过于复杂。

应该注意事项,如默认值或空对象模式。两者都可能最终隐藏错误,成为最好的猜测或治愈疾病。通常可以使用NullObject模式和Optional两种方法来简单地关闭或双向传递解释器中的内置错误处理。

默认并不总是不好,但可以落入猜测的领域。隐藏来自用户的错误最终将其设置为失败。默认(和sanitisation)总是需要仔细考虑。

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

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