本文是回答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 - 曹旭东