Java 6中复合if /或与try/catch相比的成本

Dav*_*row 7 java if-statement try-catch

如果声明,我们目前有以下化合物......

if ((billingRemoteService == null)
    || billingRemoteService.getServiceHeader() == null
    || !"00".equals(billingRemoteService.getServiceHeader().getStatusCode())
    || (billingRemoteService.getServiceBody() == null) 
    || (billingRemoteService.getServiceBody().getServiceResponse() == null) 
    || (billingRemoteService.getServiceBody().getServiceResponse().getCustomersList() == null) 
    || (billingRemoteService.getServiceBody().getServiceResponse().getCustomersList().getCustomersList() == null) 
    || (billingRemoteService.getServiceBody().getServiceResponse().getCustomersList().getCustomersList().get(0) == null) 
    || (billingRemoteService.getServiceBody().getServiceResponse().getCustomersList().getCustomersList().get(0).getBillAccountInfo() == null)
    || (billingRemoteService.getServiceBody().getServiceResponse().getCustomersList().getCustomersList().get(0).getBillAccountInfo().getEcpdId() == null)) {
        throw new WebservicesException("Failed to get information for Account Number " + accountNo);
}

return billingRemoteService.getServiceBody().getServiceResponse().getCustomersList().getCustomersList().get(0);
Run Code Online (Sandbox Code Playgroud)

这不能简化为......

try {
    //Check to be sure there is an EpcdId.
    (billingRemoteService.getServiceBody().getServiceResponse().getCustomersList().getCustomersList().get(0).getBillAccountInfo().getEcpdId();
    return billingRemoteService.getServiceBody().getServiceResponse().getCustomersList().getCustomersList().get(0);
} catch (NullPointerException npe) {
    throw new WebservicesException("Failed to get information for Account Number " + accountNo);
}
Run Code Online (Sandbox Code Playgroud)

如果是这样,Java 6下两种方法之间的"成本"差异是什么?似乎是一个非常复杂的if语句,只是为了验证所有干预调用都不为空.对于不同的帐户,此操作被多次调用.

Ste*_*n C 7

我必须不同意埃德温巴克的论点.

他说:

正如其他人所说,例外比if语句更昂贵.但是,有一个很好的理由不在您的情况下使用它们."特殊活动的例外情况"

解压缩消息时,预期错误检查不在消息中,而不是异常事件.

这基本上是说如果你进行错误检查,那么预计会出现错误(因为你正在寻找它),因此也不例外.

但这不是"特殊事件"的意思.特殊事件意味着异常/异常/不太可能发生的事件.特殊情况是关于事件发生的可能性,而不是关于您是否(或应该)期待和/或寻找事件.

因此,回到第一原则,避免异常的根本原因是成本权衡:明确测试事件的成本与抛出,捕获和处理异常的成本.确切地说.

如果事件的概率是P

  • 使用例外的平均成本是:

    P*创建/抛出/捕获/处理异常的成本+(1 - P)*没有明确测试的成本

  • 不使用例外的平均成本是:

    P*成本测试时发生的情况并进行错误处理+(1 - P)*当条件没有发生时的测试成本.

当然,这是"特殊"=="不太可能"的地方.因为,如果P接近0,使用异常的开销变得越来越不重要.如果P足够小(取决于问题),异常将更有效.


因此,在回答原始问题时,不仅仅是if/else与异常的成本.您还需要考虑您正在测试的事件(错误)的可能性.

另一件需要注意的是,JIT编译器有很多优势可以优化这两个版本.

  • 在第一个版本中,可能会重复计算子表达式,并重复幕后空检查.JIT编译器可能能够优化其中的一部分,但它取决于是否存在副作用.如果不能,则测试顺序可能相当昂贵.

  • 在第二个版本中,JIT编译器可以注意到在不使用异常对象的情况下抛出异常并在同一方法中捕获异常.由于异常对象没有"逃逸",它(理论上)可以被优化掉.如果发生这种情况,使用异常的开销几乎会消失.


(这是一个有用的例子,说明我的非正式方程式意味着什么:

  // Version 1
  if (someTest()) {
      doIt();
  } else {
      recover();
  }

  // Version 2
  try {
      doIt();
  } catch (SomeException ex) {
      recover();
  }
Run Code Online (Sandbox Code Playgroud)

和以前一样,让P成为引起异常的概率.

版本#1 - 如果我们假设someTest()测试成功失败的成本是相同的,并且在没有抛出异常时使用"doIt-success"来表示doIt的成本,那么一次执行版本的平均成本# 1是:

  V1 = cost("someTest") + P * cost("recover") + (1 - P) * cost("doIt-success")
Run Code Online (Sandbox Code Playgroud)

版本#2 - 如果我们假设doIt()无论是否抛出异常,成本是相同的,那么一次执行版本#2 的平均成本是:

  v2 = P * ( cost("doit-fail") + cost("throw/catch") + cost("recover") ) +
       (1 - P) * cost("doIt-success")
Run Code Online (Sandbox Code Playgroud)

我们从另一个中减去一个来给出平均成本的差异.

  V1 - V2 = cost("someTest") + P * cost("recover") + 
            (1 - P) * cost("doIt-success") -
            P * cost("doit-fail") - P * cost("throw/catch") -
            P * cost("recover") - (1 - P) * cost("doIt-success")

          = cost("someTest") - P * ( cost("doit-fail") + cost("throw/catch") )
Run Code Online (Sandbox Code Playgroud)

请注意,成功的成本recover()和成本将被doIt()取消.我们留下了一个积极的成分(为避免异常而进行测试的成本)和一个与失败概率成正比的负面成分.该等式告诉我们,无论抛出/捕获开销有多昂贵,如果概率P接近零,则差异将为负.


回应此评论:

您不应该捕获流控制未经检查的异常的真正原因是:如果您调用的方法之一抛出NPE会发生什么?你捕获NPE假设它来自你的代码,当它可能来自其中一个getter.您可能隐藏了代码下面的错误,这可能导致大量的调试问题(个人经验).当您可能通过捕获未经检查的异常(如NPE或IOOBE)进行流控制时,性能参数在您(或其他人)代码中隐藏错误时无用.

这与Edwin Bucks的论点非常相似.

问题是"流量控制"是什么意思?

  • 一方面,抛出和捕获异常是流量控制的一种形式.这意味着你不应该抛出并捕获未经检查的异常.这显然毫无意义.

  • 因此,我们回过头来讨论不同类型的流量控制,这与"特殊"与"非特殊"之类的争论完全相同.

我认识到你在捕捉NPE时需要小心谨慎,以确保你没有捕捉到来自意外来源的东西(即不同的bug).但在OP的例子中,风险很小.你可以而且应该检查看起来像简单吸气剂的那些东西真的是简单的吸气剂.

而且你还必须认识到,捕获NPE(在这种情况下)会产生更简单的代码,这可能比if语句中的长序列条件更可靠.请记住,这种"模式"可以在很多地方复制.

底线是异常和测试之间的选择可能很复杂.一个简单的口头禅,告诉你总是使用测试,在某些情况下会给你错误的解决方案.并且"错误"可能不太可靠和/或可读性较差和/或代码较慢.


Ant*_*oly 0

根据经验,异常处理比 ifs 更昂贵,但我同意 TheZ 的观点,即最好的方法是在预期负载下对两个版本进行基准测试/分析。当您考虑 IO 和网络成本时,这种差异可能会变得可以忽略不计,而 IO 和网络成本通常会按数量级推断 CPU 成本。另外,请注意,!00.equals可能应该在第二个版本中检查条件。