Java if 与 try/catch 开销
Java 中使用 try/catch 块(与 if 块 相比)是否有任何开销(假设所附代码不要求如此)?
例如,采用以下两个字符串“安全修剪”方法的简单实现:
public String tryTrim(String raw) {
try {
return raw.trim();
} catch (Exception e) {
}
return null;
}
public String ifTrim(String raw) {
if (raw == null) {
return null;
}
return raw.trim();
}
如果 raw
输入很少为 null
,是否存在性能差异两种方法之间?
此外,使用 tryTrim()
方法来简化代码布局是一个很好的编程模式吗?尤其是当许多 if 块很少检查时通过将代码包含在一个 try/catch 块中可以避免错误情况吗?
例如,一种常见的情况是有一个具有 N 个参数
的方法,该方法在其开始处使用其中的 M <= N
个参数,如果有任何此类参数,则会快速且确定性地失败参数“无效”(例如,null 或空字符串),而不影响代码的其余部分。
在这种情况下,不必编写 k * M
if 块(其中 k
是每个参数的平均检查次数,例如 k = 2
对于 null 或空字符串),try/catch 块将显着缩短代码,并且可以使用 1-2 行注释来明确注释“非常规”逻辑。
这种模式也会加速该方法,特别是如果错误情况很少发生,并且不会影响程序安全(假设错误情况是“正常的”,例如在字符串处理方法中,其中 null 或空值)是可以接受的,尽管很少出现)。
Is there any overhead in Java for using a try/catch block, as opposed to an if block (assuming that the enclosed code otherwise does not request so)?
For example, take the following two simple implementations of a "safe trim" method for strings:
public String tryTrim(String raw) {
try {
return raw.trim();
} catch (Exception e) {
}
return null;
}
public String ifTrim(String raw) {
if (raw == null) {
return null;
}
return raw.trim();
}
If the raw
input is only rarely null
, is there any performance difference between the two methods?
Furthermore, is it a good programming pattern to use the tryTrim()
approach for simplifying the layout of code, especially when many if blocks checking rare error conditions can be avoided by enclosing the code in one try/catch block?
For example, it is a common case to have a method with N parameters
, which uses M <= N
of them near its start, failing quickly and deterministically if any such parameter is "invalid" (e.g., a null or empty string), without affecting the rest of the code.
In such cases, instead of having to write k * M
if blocks (where k
is the average number of checks per parameter, e.g. k = 2
for null or empty strings), a try/catch block would significantly shorten the code and a 1-2 line comment could be used to explicitly note the "unconventional" logic.
Such a pattern would also speed up the method, especially if the error conditions occur rarely, and it would do so without compromising program safety (assuming that the error conditions are "normal", e.g. as in a string processing method where null or empty values are acceptable, albeit seldom in presence).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
我知道您问的是性能开销,但您确实不应该互换使用
try
/catch
和if
。try
/catch
用于处理超出您控制范围且不属于正常程序流程的错误。例如,尝试写入文件而文件系统已满?这种情况通常应该使用try
/catch
来处理。if
语句应该是正常的流程和普通的错误检查。例如,用户无法填充必填输入字段?使用if
,而不是try
/catch
。在我看来,您的示例代码强烈建议正确的方法是
if
语句,而不是try
/catch
。为了回答您的问题,我推测
try
/catch
中的开销通常比if
中的开销更多。要确定答案,请获取 Java 分析器并找出您关心的特定代码。答案可能会根据具体情况而有所不同。I know you're asking about performance overhead, but you really should not use
try
/catch
andif
interchangeably.try
/catch
is for things that go wrong that are outside of your control and not in the normal program flow. For example, trying to write to a file and the file system is full? That situation should typically be handled withtry
/catch
.if
statements should be normal flow and ordinary error checking. So, for example, user fails to populate a required input field? Useif
for that, nottry
/catch
.It seems to me that your example code strongly suggests that the correct approach there is an
if
statement and not atry
/catch
.To answer your question, I would surmise that there is generally more overhead in a
try
/catch
than anif
. To know for sure, get a Java profiler and find out for the specific code you care about. It's possible that the answer may vary depending on the situation.这个问题几乎已经被“回答得要死”,但我认为还有几点可以有用地提出:
使用
try / catch
进行非异常控制流是不好的风格(在爪哇)。 (人们经常争论“非例外”的含义......但这是一个不同的话题。)这种风格不好的部分原因是
try / catch
是 比常规控制流语句1贵几个数量级。实际差异取决于程序和平台,但我预计它会贵 1000 倍或更多倍。除此之外,创建异常对象捕获堆栈跟踪,查找并复制堆栈上每个帧的信息。堆栈越深,需要复制的内容就越多。它的风格不好的另一个原因是代码更难阅读。
1 - 最近版本的 Java 中的 JIT 可以优化异常处理,从而在某些情况下大幅减少开销。但是,默认情况下不会启用这些优化。
您编写示例的方式也存在问题:
捕获
Exception
是非常糟糕的做法,因为有一个您有可能会意外捕获其他未经检查的异常。例如,如果您围绕调用raw.substring(1)
执行此操作,您还会捕获潜在的StringIndexOutOfBoundsException
... 并隐藏错误。您的示例尝试执行的操作(可能)是处理
null
字符串时实践不当的结果。作为一般原则,您应该尽量减少null
字符串的使用,并尝试限制它们(有意的)传播。如果可能,请使用空字符串而不是null
来表示“无值”。当您确实需要传递或返回null
字符串时,请在方法javadoc中清楚地记录它。如果您的方法在不应调用时使用null
进行调用...这是一个错误。让它抛出异常。不要尝试通过(在本示例中)返回null
来弥补错误。...我的回答中的大部分要点都与
null
值无关!是的,在某些情况下需要
null
值,您需要处理它们。但我认为,
tryTrim()
正在做的事情(通常)是处理null
的错误方法。比较这三段代码:最终,您必须以与常规字符串不同的方式处理
null
,并且通常最好尽快执行此操作。允许null
从其原点传播得越远,程序员就越有可能忘记null
值是一个可能性,并编写假设非空值的错误代码。忘记 HTTP 请求参数可能会丢失(即param == null
)是发生这种情况的典型情况。我并不是说 tryTrim() 本质上是不好的。但事实上,您觉得需要像这样编写方法,这可能表明空处理不太理想。
最后,还有其他方法可以在 API 中对“没有值”进行建模。其中包括:
null
,并引发异常或替换不同的值。isSet
方法...如果结果为null
,则在get
中引发异常。null
字符串、集合或数组1。可选
。1 - 请注意,这与空对象模式不同,因为不一定只有一个类型实例来表示“无效”。但如果你遵守纪律,你可以将这两种想法结合起来。
This question has almost been "answered to death", but I think there are a few more points that could usefully be made:
Using
try / catch
for non-exceptional control flow is bad style (in Java). (There is often debate about what "non-exceptional" means ... but that's a different topic.)Part of the reason it is bad style is that
try / catch
is orders of magnitude more expensive than an regular control flow statement1. The actual difference depends on the program and the platform, but I'd expect it to be 1000 or more times more expensive. Among other things, the creation the exception object captures a stack trace, looking up and copying information about each frame on the stack. The deeper the stack is, the more that needs to be copied.Another part of the reason it is bad style is that the code is harder to read.
1 - The JIT in recent versions of Java can optimize exception handling to drastically reduce the overheads in some cases. However, these optimizations are not enabled by default.
There are also issues with the way that you've written the example:
Catching
Exception
is very bad practice, because there is a chance that you will catch other unchecked exceptions by accident. For instance, if you did that around a call toraw.substring(1)
you would also catch potentialStringIndexOutOfBoundsException
s ... and hide bugs.What your example is trying to do is (probably) a result of poor practice in dealing with
null
strings. As a general principle, you should try to minimize the use ofnull
strings, and attempt to limit their (intentional) spread. Where possible, use an empty string instead ofnull
to mean "no value". And when you do have a case where you need to pass or return anull
string, document it clearly in your method javadocs. If your methods get called with anull
when they shouldn't ... it is a bug. Let it throw an exception. Don't try to compensate for the bug by (in this example) returningnull
.... and most of the points in my answer are not about
null
values!Yes there are situations where
null
values are expected, and you need deal with them.But I would argue that what
tryTrim()
is doing is (typically) the wrong way to deal withnull
. Compare these three bits of code:Ultimately you have to deal with the
null
differently from a regular string, and it is usually a good idea do this as soon as possible. The further thenull
is allowed to propagate from its point origin, the more likely it is that the programmer will forget that anull
value is a possibility, and write buggy code that assumes a non-null value. And forgetting that an HTTP request parameter could be missing (i.e.param == null
) is a classic case where this happens.I'm not saying that
tryTrim()
is inherently bad. But the fact that you feel the need write methods like this is probably indicative of less than ideal null handling.Finally, there are other ways to model "there isn't a value" in an API. These include:
null
in setters and constructors, and either throw an exception or substitute a different value.is<Field>Set
method ... and throwing an exception inget<Field>
if the result would benull
.null
strings, collections or arrays1.Optional
.1 - To mind, this is different from the Null Object Pattern because there isn't necessarily just one instance of the type to denote "nullity". But you can combine the two ideas ... if you are disciplined about it.
使用第二个版本。当有其他替代方案可用时,切勿使用异常来控制流,因为这不是它们的用途。例外是指特殊情况。
在讨论这个主题时,不要在这里捕捉
Exception
,尤其不要吞下它。在您的情况下,您会期望出现 NullPointerException。如果你要抓住什么,那就是你会抓住的(但是回到第一段,不要这样做)。当您捕获(并吞下!)Exception
时,您是在说“无论发生什么问题,我都可以处理它。我不在乎它是什么。”您的程序可能处于不可撤销的状态!只捕获您准备处理的内容,让其他所有内容传播到可以处理它的层,即使该层是顶层并且它所做的只是记录异常然后点击弹出开关。Use the second version. Never use exceptions for control flow when other alternatives are available, as that is not what they are there for. Exceptions are for exceptional circumstances.
While on the topic, do not catch
Exception
here, and especially do not swallow it. In your case, you would expect aNullPointerException
. If you were to catch something, that is what you would catch (but go back to paragraph one, do not do this). When you catch (and swallow!)Exception
, you are saying "no matter what goes wrong, I can handle it. I don't care what it is." Your program might be in an irrevocable state! Only catch what you are prepared to deal with, let everything else propogate to a layer that can deal with it, even if that layer is the top layer and all it does is log the exception and then hit the eject switch.否则,异常抛出和捕获的速度很快(尽管
if
可能仍然更快),但缓慢的是创建异常的堆栈跟踪,因为它需要遍历所有当前堆栈。 (一般来说,使用异常来控制流是不好的,但是当确实需要并且异常必须很快时,可以通过重写 Throwable.fillInStackTrace() 方法来跳过构建堆栈跟踪,或者保存一个异常实例并重复抛出它,而不是总是创建一个新的异常实例。)Otherwise exceptions are fast to throw and catch (though an
if
is probably still faster), but the slow thing is creating the exception's stack trace, because it needs to walk through all of the current stack. (In general it's bad to use exceptions for control flow, but when that really is needed and the exceptions must be fast, it's possible to skip building the stack trace by overriding theThrowable.fillInStackTrace()
method, or to save one exception instance and throw it repeatedly instead of always creating a new exception instance.)至于开销,您可以自己测试:
}
我得到的数字是:
至于什么风格 - 这是一个完全独立的问题。 if 语句看起来很自然,但由于多种原因, try 看起来非常奇怪:
- 即使您正在检查 NULL 值,您也捕获了异常,您是否期望发生“异常”的事情(否则捕获 NullException)?
- 你发现了这个异常,你会报告它还是吞下去?
等等 等等
编辑:请参阅我的评论,了解为什么这是一个无效的测试,但我真的不想将其留在这里。只需交换 tryTrim 和 ifTrim,我们突然得到以下结果(在我的机器上):
无需开始解释所有这些,只需阅读 this 作为开始 - Cliff 也有一些关于整个主题的很棒的幻灯片,但我目前找不到链接。
了解 Hotspot 中异常处理的工作原理后,我相当确定在正确的测试中(没有异常的 try/catch)将是基准性能(因为没有任何开销),但是 JIT 可以通过空指针检查来玩一些技巧,然后方法调用(没有显式检查,但如果对象为 null,则捕获硬件异常),在这种情况下我们会得到相同的结果。
另外不要忘记:我们正在讨论一个容易预测的差异,如果这将是一个 CPU 周期!修剪电话的费用将是这个数字的一百万倍。
As far as overhead goes, you can test for yourself:
}
The numbers I got are:
As far as what style - it is a whole separate question. The if statement looks pretty natural, but the try looks really strange for multiple reason:
- you caught Exception even though you are checking for NULL value, are you expecting something "exceptional" to happen (otherwise catch NullException)?
- you caught that Exception, are you going to report it or swallow?
etc. etc. etc.
Edit: See my comment for why this is an invalid test, but I really didn't want to leave this standing here. Just by swapping tryTrim and ifTrim, we suddenly get the following results (on my machine):
Instead of starting to explain all of this, just read this for the beginning - Cliff also has some great slides about the whole topic, but I can't find the link at the moment.
Knowing how exception handling works in Hotspot, I'm fairly certain that in a correct test try/catch without an exception) would be the baseline performance (because there's no overhead whatsoever), but the JIT can play some tricks with Nullpointer checks followed by method invocations (no explicit check, but catch the hw exception if the object is null) in which case we'd get the same result.
Also don't forget: We're talking about the difference of one easily predictable if which would be ONE CPU cycle! The trim call will cost a million times that.