Java 6中复合if/or与try/catch的成本

Java 6中复合if/or与try/catch的成本,java,if-statement,try-catch,Java,If Statement,Try Catch,我们目前有以下复合if语句 if ((billingRemoteService == null) || billingRemoteService.getServiceHeader() == null || !"00".equals(billingRemoteService.getServiceHeader().getStatusCode()) || (billingRemoteService.getServiceBody() == null) || (billi

我们目前有以下复合if语句

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);
这不能简化为

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);
}

如果是这样,在Java6下,这两种方法之间的“成本”差异是什么?这似乎是一个相当复杂的if语句,用于验证所有中间调用都不是null。对于不同的帐户,此操作会被多次调用。

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

基本上if/else和try-catch是不同的。这些东西的性能/成本与您的代码无关。一旦您正确地设计和实现了业务规则,您就可以担心性能了

try/catch用于程序异常状态的异常处理。而if-else用于程序的条件状态

它们有自己的用途。查看所需内容并相应地编码


而且,你违反了德米特的法律。如果您仔细检查设计和代码,使其更易于阅读,并使其成为更好的可维护性和可扩展性设计,那将更好

正如其他人所说,异常比if语句更昂贵。但是,有一个很好的理由不在您的案例中使用它们

例外情况适用于例外事件

解包消息时,消息中不存在的内容应为错误检查,而不是异常事件

这段代码对其他实例中的数据太感兴趣了。向这些其他实例添加一些行为。现在,所有的行为都在不在类中的代码中,这是错误的对象定向

-- for billingRemoteService --
public boolean hasResponse();
public BillingRemoteResponse getResponse();

-- for BillingRemoteResponse --
public List<Customer> getCustomerList();

-- for Customer --
public Customer(Long ecpdId, ...) {
  if (ecpdId == null) throw new IllegalArgumentException(...);
}
——用于billingRemoteService--
公共布尔hassresponse();
public BillingRemoteResponse getResponse();
--用于BillingRemoteResponse--
公共列表getCustomerList();
--为顾客--
公共客户(长ecpdId,…){
如果(ecpdId==null)抛出新的IllegalArgumentException(…);
}

您可以将Groovy混合到基于JVM的应用程序中,这样做会大大简化:

def result = billingRemoteService?.
  serviceBody?.
  serviceResponse?.
  customersList?.
  customersList[0];
if ('00' != billingRemoteService?.serviceHeader?.statusCode ||
   result?.
   billAccountInfo?.
   getEcpdId == null)
  throw new WebServicesException
...

好的,没有一个答案真正回答了这个问题,尽管根据我目前的情况,theZ的建议是检查这个问题的最快方法。这些代码都不是我设计或编写的,而且它所属的应用程序非常庞大,这意味着要花上数年的时间进行重构,才能处理像这样的各种情况

因此,对于每个人的启迪:

我快速进行了一个测试,模拟了两种方法所需的类。我不在乎类的任何单个方法运行多长时间,因为这与我的问题无关。我还使用JDK1.6和1.7构建/运行。这两个JDK之间几乎没有区别

如果一切正常(任何地方都没有空值),则平均时间为:

Method A (compound IF):  4ms
Method B (exceptions):   2ms
因此,当对象不为null时使用异常的速度是复合异常的两倍

如果我故意在get(0)语句中强制执行空指针异常,事情会变得更加有趣

这里的平均数是:

Method A: 36ms
Method B:  6ms

因此,很明显,在最初记录的案例中,从成本角度来看,例外是可行的。

我一定不同意Edwin Buck的论点

他说:


正如其他人所说,异常比if语句更昂贵。但是,有一个很好的理由不在您的案例中使用它们。“例外情况适用于例外事件”

解包消息时,消息中不存在的内容应为错误检查,而不是异常事件

这本质上是说,如果您进行错误检查,那么错误是预期的(因为您正在寻找它),因此不是例外

但这并不是“特殊事件”的意思。异常事件是指异常/异常/不可能发生的事件。例外是指事件发生的可能性,而不是你是否(或应该)期待和/或期待它

因此,回到第一原则,避免异常的基本理由是成本权衡:显式测试事件的成本与抛出、捕获和处理异常的成本。准确地说

如果事件发生的概率为p

  • 使用异常的平均成本为:

    p*创建/抛出/捕获/处理异常的成本+(1-p)*无显式测试的成本

  • 不使用异常的平均成本为:

    p*条件发生时的成本测试和错误处理+(1-p)*条件未发生时的测试成本

当然,这就是“例外”=“不可能”的原因。因为,如果P接近0,那么使用异常的开销就会越来越小。如果P足够小(取决于问题),异常将更有效


因此,在回答最初的问题时,这不仅仅是if/else相对于异常的成本。您还需要考虑您正在测试的事件(错误)的可能性

另一件需要注意的事情是,JIT编译器在优化这两个版本方面有很大的空间

  • 在第一个版本中,可能存在大量子表达式的重复计算,以及重复的幕后空值检查。JIT编译器可能能够优化其中的一些,尽管这取决于是否有副作用。如果不能,那么测试的顺序可能会相当昂贵

  • 在第二个版本中,有JIT c的作用域
      // Version 1
      if (someTest()) {
          doIt();
      } else {
          recover();
      }
    
      // Version 2
      try {
          doIt();
      } catch (SomeException ex) {
          recover();
      }
    
      V1 = cost("someTest") + P * cost("recover") + (1 - P) * cost("doIt-success")
    
      v2 = P * ( cost("doit-fail") + cost("throw/catch") + cost("recover") ) +
           (1 - P) * cost("doIt-success")
    
      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") )
    
    import java.util.List;
    import java.util.Optional;
    
    public class Optionals
    {
        interface BillingRemoteService
        {Optional<ServiceBody> getServiceBody();}
    
        interface ServiceBody
        {Optional<ServiceResponse> getServiceResponse();}
    
        interface ServiceResponse
        {Optional<List<Customer>> getCustomersList();}
    
        interface Customer
        {Optional<BillAccountInfo> getBillAccountInfo();}
    
        interface BillAccountInfo
        {Optional<EcpId> getEcpdId();}
    
        interface EcpId
        {Optional<BillingRemoteService> getEcpdId();}
    
        Object test(BillingRemoteService billingRemoteService) throws Exception
        {
            return
            billingRemoteService.getServiceBody()
            .flatMap(ServiceBody::getServiceResponse)
            .flatMap(ServiceResponse::getCustomersList)
            .map(l->l.get(0))
            .flatMap(Optional::ofNullable)
            .flatMap(Customer::getBillAccountInfo)
            .flatMap(BillAccountInfo::getEcpdId).orElseThrow(()->new Exception("Failed to get information for Account Number "));
        }
    }