使用 try / catch 块的Java是否有任何开销,而不是 if block (假设所附的代码没有请求)?
例如,对字符串采用以下两种简单的“安全修剪”方法:
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 blocks 检查罕见的错误条件时可以通过将代码放在一个try / catch块中来避免吗?
例如,常见的情况是使用 在这种情况下,不必写 这样的模式也会加速该方法,特别是如果错误条件很少发生,并且它会这样做而不会影响程序安全性(假设错误条件是“正常”的,例如在字符串处理方法中为null或者空值是可以接受的,尽管很少存在。)N parameters
的方法,在其开始附近使用M <= N
,快速且确定性地失败是否有任何此类参数“无效”(例如, k * M
if blocks (其中k
是每个参数的平均检查次数,例如k = 2
为null或者空字符串), try / catch 块会显着缩短代码,并且可以使用1-2行注释来明确地注意“非常规”逻辑。
答案 0 :(得分:60)
我知道您在询问性能开销,但实际上您不应该交替使用try
/ catch
和if
。
try
/ catch
适用于您无法控制但不在正常程序流程中的错误。例如,尝试写入文件并且文件系统已满?通常应使用try
/ catch
来处理这种情况。
if
语句应该是正常流程和普通错误检查。那么,例如,用户无法填充所需的输入字段?请使用if
,而不是try
/ catch
。
在我看来,您的示例代码强烈建议正确的方法是if
语句,而不是try
/ catch
。
要回答您的问题,我猜测try
/ catch
的费用通常比if
更高。要确定,请获取Java分析器并找出您关心的特定代码。答案可能会根据具体情况而有所不同。
答案 1 :(得分:33)
这个问题几乎已经“回答死亡”,但我认为还有一些问题可以有用:
将try / catch
用于非异常控制流是不好的样式(在Java中)。 (关于“非例外”的含义经常存在争议......但这是一个不同的主题。)
风格不好的部分原因是try / catch
数量级比常规控制流程语句 1 更昂贵。实际的差异取决于程序和平台,但我预计它会高出1000倍或更多倍。除此之外, creation 异常对象捕获堆栈跟踪,查找并复制有关堆栈上每个帧的信息。堆栈越深,需要复制的越多。
另一部分原因是代码难以阅读。
1 - 在某些情况下,Java 7的最新版本中的JIT可以优化异常处理,从而大幅减少开销。但是,默认情况下不会启用这些优化。
您编写示例的方式也存在问题:
捕获Exception
是非常糟糕的做法,因为您可能会意外地捕获其他未经检查的异常。例如,如果您在调用raw.substring(1)
时执行此操作,则还会捕获潜在的StringIndexOutOfBoundsException
...以及隐藏错误。
你的例子试图做的(可能)是处理null
字符串的不良做法的结果。作为一般原则,您应该尽量减少null
字符串的使用,并尝试限制它们(有意)的传播。在可能的情况下,使用空字符串而不是null
来表示“无值”。当你确实遇到需要传递或返回null
字符串的情况时,请在方法javadocs中清楚地记录它。如果你的方法在null
被调用时不应该......那就是一个bug。让它抛出异常。不要试图通过(在此示例中)返回null
来补偿错误。
<强>后续强>
我的问题更为笼统,不仅仅是空值。
...我的答案中的大多数要点都与空值无关!
但请记住,在许多情况下,您希望允许偶尔出现空值或任何其他可能产生异常的值,并忽略它们。例如,当从某处读取键/对值并将它们传递给上面的tryTrim()之类的方法时就是这种情况。
是的,有些情况下需要null
值,您需要处理它们。
但我认为tryTrim()
正在做的事情(通常)是处理null
的错误方式。比较这三位代码:
// Version 1
String param = httpRequest.getParameter("foo");
String trimmed = tryTrim(param);
if (trimmed == null) {
// deal with case of 'foo' parameter absent
} else {
// deal with case of 'foo' parameter present
}
// Version 2
String param = httpRequest.getParameter("foo");
if (param == null) {
// deal with case of 'foo' parameter absent
} else {
String trimmed = param.trim();
// deal with case of 'foo' parameter present
}
// Version 3
String param = httpRequest.getParameter("foo");
if (param == null) {
// treat missing and empty parameters the same
param = "";
}
String trimmed = param.trim();
最终,你必须以不同于常规字符串的方式处理null
,并且通常一个好主意尽快做到这一点。允许null
从其原点传播越远,程序员就越有可能忘记 null
值是可能的,并编写错误的代码假定非空值。并且忘记了HTTP请求参数可能丢失(即param == null
)是发生这种情况的经典案例。
我不是说tryTrim()
天生就是坏事。但是程序员认为需要这样的写方法的事实是可能表示不太理想的空值处理。
答案 2 :(得分:9)
使用第二个版本。当其他替代品可用时,切勿使用控制流的例外情况,因为这不是它们的用途。例外情况适用于特殊情况。
关于这个主题,请不要在这里抓住Exception
,尤其是不要吞下它。在您的情况下,您会期望NullPointerException
。如果你要抓住某些东西,那就是你会抓住的东西(但是回到第一段,做不这样做)。当你抓住(并吞下!)Exception
时,你说“不管出了什么问题,我都可以处理它。我不在乎它是什么。”您的程序可能处于不可撤销的状态!只捕捉你准备处理的内容,让其他所有内容传播到可以处理它的层,即使该层是顶层,它所做的只是记录异常,然后点击弹出开关。
答案 3 :(得分:4)
否则抛出和捕获的异常很快(尽管if
可能仍然更快),但缓慢的是创建异常的堆栈跟踪,因为它需要遍历所有当前堆栈。 (一般来说,使用控制流的异常是不好的,但是当真正需要它并且异常必须很快时,可以通过覆盖Throwable.fillInStackTrace()
方法跳过构建堆栈跟踪,或者保存一个异常实例和重复抛出它而不是总是创建一个新的异常实例。)
答案 4 :(得分:-3)
就开销而言,你可以自己测试一下:
public class Overhead {
public static void main(String[] args) {
String testString = "";
long startTimeTry = System.nanoTime();
tryTrim(testString);
long estimatedTimeTry = System.nanoTime() - startTimeTry;
long startTimeIf = System.nanoTime();
ifTrim(testString);
long estimatedTimeIf = System.nanoTime() - startTimeIf;
System.out.println("Try time:" + estimatedTimeTry);
System.out.println("If time:" + estimatedTimeIf);
}
public static String tryTrim(String raw) {
try {
return raw.trim();
} catch (Exception e) {
}
return null;
}
public static String ifTrim(String raw) {
if (raw == null) {
return null;
}
return raw.trim();
}
}
我得到的数字是:
Try time:8102
If time:1956
至于什么样式 - 这是一个完全独立的问题。 if语句看起来很自然,但由于多种原因,try看起来很奇怪: - 即使你正在检查NULL值,你捕获了Exception,你是否期望发生“异常”的事情(否则捕获NullException)? - 你抓住了那个例外,你要报告还是吞下去? 等等。
编辑:请参阅我的评论,为什么这是一个无效的测试,但我真的不想让这个站在这里。只需交换tryTrim和ifTrim,我们就会突然得到以下结果(在我的机器上):
Try time:2043
If time:6810
而不是开始解释所有这些,只需阅读this作为开头 - Cliff也有一些关于整个主题的精彩幻灯片,但我目前找不到链接。
了解异常处理在Hotspot中是如何工作的,我相当肯定在正确的测试中try / catch没有异常)将是基线性能(因为没有任何开销),但是JIT可以使用Nullpointer进行一些技巧检查后跟方法调用(没有显式检查,但如果对象为null则捕获hw异常),在这种情况下我们会得到相同的结果。 另外不要忘记:我们正在谈论一个容易预测的区别,如果哪个是一个CPU周期!修剪调用将花费一百万倍。