Java异常有多慢?

本文是回答StackOverflow上的问题,但因为写太长了,所以就发到这里了。

实际上,真正要讨论的问题并不是,“相对‘那些不会发生错误的代码’来说,‘那些以异常形式上报的错误’会有多慢?”,因为你可能也认同“已接受的回答”。相反,真正的问题是,“相对‘那些以其他形式上报的错误’来说,‘那些以异常形式上报的错误’会有多慢?”

通常认为,“不要抛出你想要捕获的异常”。所以,抛出一个其他人——如平台或框架API——要捕获的异常是合适的。或者在编写一些工具API时,抛出异常也可以的,如日志记录或消息发送,这些操作需要处理外部虚拟机的错误,例如文件IO或网络IO错误。

这是适合抛出异常的例子,应该没有人会在这些例子上有争议。现在,看一下简单方法中出现错误时会发生什么。假设方法签名如下:

    /**
     * Transforms SomeClass into SomeOtherClass.
     * @param input some class instance
     * @return the transformed instance,
     *         or null if the transformation was unsuccessful
     */
    public SomeOtherClass transform(SomeClass input)

调用该方法的代码如下所示:

    SomeClass input = ... // get the input from somewhere
    SomeOtherClass result = transform(input);
    if(result == null) {
       // handle the failure
    } else {
       // use the result
    }

但现在,当方法返回null时,我们想知道哪里出现错误了。简单来说可以这样:

    /**
     * Transforms SomeClass into SomeOtherClass.
     * @param input some class instance
     * @return the transformed instance, never null
     * @throws TransformationException on failure
     */
    public SomeOtherClass transform(SomeClass input) throws TransformationException

为了实现这个功能,你需要将“return null”改为“return new TransformationException(“reason”);”。调用方法需要做改动么?

    SomeClass input = ... // get the input from somewhere
    try {
       SomeOtherClass result = transform(input);
       // use the result
    } catch(TransformationException e) {
       // handle the failure
    }

没有人会去读上面的代码块,没什么意义。所以也没什么可惊讶的。你可能每天都在写类似的代码,但也说不上是“代码异味”。可是,将来你会明白在“已预料到”的错误上使用异常是多么大的错误假设有一天你开始读到在“已预料到”的错误上使用异常是非常不好的。现在,捕获“未预料到”异常是非常可笑的,因为编写catch代码块,并显式的处理异常本身就是预料到会有异常。但没关系,我们还可以修改代码改变这种窘境。于是,我们退回到了C语言时代,返回一个异常值来表示错误。

    /**
     * Transforms SomeClass into SomeOtherClass.
     * @param input some class instance
     * @return the SomeOtherClass instance or, 
     *    if the transform fails, a TransformFailure instance 
     */
    public Object transform(SomeClass input)

于是,调用部分的代码也不得不奇葩一些:

    SomeClass input = ... // get the input from somewhere
    Object result = transform(input);
    if(result instanceof SomeOtherClass) {
       SomeOtherClass success = (SomeOtherClass)result;
       // use the result
    } else {
       TransformFailure failure = (TransformFailure)result;
       // handle the failure
    }

所以,如果你觉着使用异常有代码异味,但可以接受打破类型安全,那么你最终要面对的是难以维护,没法使用,仅仅比基于异常的解决方案快两倍的代码。但是你又不能接受类型安全被破坏,因为这2倍的性能提升还未被证明,现在就用实在太鲁莽。所以,你决定使用结果对象,而不是返回异常值。

   /**
     * Transforms SomeClass into SomeOtherClass.
     * @param input some class instance
     * @return the TransformResult instance
     */
    public TransformResult transform(SomeClass input)

现在,相应地,调用部分的代码变成了这样:

    SomeClass input = ... // get the input from somewhere
    TransformResult result = transform(input);
    if(result.isSuccess()) {
       SomeOtherClass success = result.getSuccess();
       // use the result
    } else {
       TransformFailure failure = result.getFailure();
       // handle the failure
    }

现在,我们有了一个隐晦,但可管理的解决方案。可是,它比使用异常的第一个方案慢2倍,即使你将TransformResult和TransformFailure合并也是一样的。再说明一遍,使用结果对象比使用异常慢,即使在调用过程中发生了错误。每次你都需要创建一个新的结果对象,这没什么实际意义,而异常对象只在发生错误的时候才会创建。

对于异常,还有一个要讨论的地方。假设有人在使用方法transform时,没有认真看javadoc。在使用异常的例子中,会有下面的代码:

    SomeClass input = ... // get the input from somewhere
    SomeOtherClass result = transform(input);
    // use the result

在使用异常的例子中,他们知道返回值的类型,以及是否一个“已检查异常”,他们可能会得到一个编译时错误,或者他们会在throws语句中声明相应的异常。即使是“未检查异常”,错误会传递到上层调用。现在,考虑使用异常返回值的例子:

    SomeClass input = ... // get the input from somewhere
    SomeOtherClass result = (SomeOtherClass)transform(input);
    // use the result

这个粗心的用户写的代码看起来挺漂亮,但当运行过程中发生错误时,就满不是那么回事了。那时,你费尽力气提供的错误信息会因为发生了ClassCastException异常为全部丢失。使用结果对象也不会好到哪去。

    SomeClass input = ... // get the input from somewhere
    SomeOtherClass result = transform(input).getSuccess();
    // use the result

再说一遍,上面的代码看来相当正常。如果他们盲目使用本文中给出的第一个方法,那么在程序运行过程中,肯定会出现NullPointerException异常。

这里主要想说的是,处理逻辑错误时,使用异常的例子可以按预想的方式正常工作,报告错误信息。但是其他的解决方案却会产生一些没用的异常,即使你已经正确将软件重新部署了一遍,它仍然会出错,只有这时,你才能得到错误信息。

所以,唯一符合逻辑性的结论是,如果你想上报错误信息,那么就应该使用异常。它比结果对象性能高,比异常返回值安全,而且运行稳定。

 

更新于2012-12-19 19:23

感谢pony指出的译文中的错误。

 

 英文原文:How slow are Java Exception,翻译:ImportNew - 曹旭东

译文链接:http://www.importnew.com/1480.html

【如需转载,请在正文中标注并保留原文链接、译文链接和译者等信息,谢谢合作!】

关于作者: 曹旭东

码农一枚,热爱技术的技术菜鸟

查看曹旭东的更多文章 >>



相关文章

发表评论

Comment form

(*) 表示必填项

5 条评论

  1. pony 说道:

    可是,将来你会明白在“已预料到”的错误上使用异常是多么大的错误。
    这一句话翻译错了吧?
    But let’s say that one day you start reading about how bad it is to use exceptions on “expected” failures.

    我感觉原作者的意思应该是:
    可是,让我们假设有一天你开始读到在“已预料到”的错误上使用异常是非常不好的。

    作者应该是假设我们接受异常不好,所以才会有后面一长段分析,但是最后发现异常还是最好的。

    Thumb up 0 Thumb down 0

  2. 曹旭东 说道:

    @pony
    感谢指出错误,已修正

    Thumb up 0 Thumb down 0

  3. MiNG 说道:

    异常其实是个好东西,说性能不会差得了多少,但是对健壮性和错误处理是一个很大的提升。
    而且很多时候,异常代表了一个返回值值域以外的情况,就像需要返回一个int的时候因为某些原因而无法得出这个值,这时异常就派上用场了(因为你总不可能返回个-1来表示错误,当返回的int是全值域有意义的时候)。

    Thumb up 0 Thumb down 0

  4. netatomy 说道:

    “快2倍”表达不准确,比 XXX 快2倍,意思是“是XXX的3倍快”。

    Thumb up 0 Thumb down 0

跳到底部
返回顶部