Java if 与 try/catch 开销

发布于 2024-12-22 12:02:36 字数 1037 浏览 0 评论 0原文

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

揪着可爱 2024-12-29 12:02:36

我知道您问的是性能开销,但您确实不应该互换使用 try/catchif

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 and if 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 with try/catch.

if statements should be normal flow and ordinary error checking. So, for example, user fails to populate a required input field? Use if for that, not try/catch.

It seems to me that your example code strongly suggests that the correct approach there is an if statement and not a try/catch.

To answer your question, I would surmise that there is generally more overhead in a try/catch than an if. 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.

孤寂小茶 2024-12-29 12:02:36

这个问题几乎已经被“回答得要死”,但我认为还有几点可以有用地提出:

  • 使用 try / catch 进行非异常控制流是不好的风格(在爪哇)。 (人们经常争论“非例外”的含义......但这是一个不同的话题。)

  • 这种风格不好的部分原因是 try / catch比常规控制流语句1贵几个数量级。实际差异取决于程序和平台,但我预计它会贵 1000 倍或更多倍。除此之外,创建异常对象捕获堆栈跟踪,查找并复制堆栈上每个帧的信息。堆栈越深,需要复制的内容就越多。

  • 它的风格不好的另一个原因是代码更难阅读。

1 - 最近版本的 Java 中的 JIT 可以优化异常处理,从而在某些情况下大幅减少开销。但是,默认情况下不会启用这些优化。

您编写示例的方式也存在问题:

  • 捕获 Exception 是非常糟糕的做法,因为有一个您有可能会意外捕获其他未经检查的异常。例如,如果您围绕调用 raw.substring(1) 执行此操作,您还会捕获潜在的 StringIndexOutOfBoundsException ... 并隐藏错误。

  • 您的示例尝试执行的操作(可能)是处理 null 字符串时实践不当的结果。作为一般原则,您应该尽量减少 null 字符串的使用,并尝试限制它们(有意的)传播。如果可能,请使用空字符串而不是 null 来表示“无值”。当您确实需要传递或返回null字符串时,请在方法javadoc中清楚地记录它。如果您的方法在不应调用时使用 null 进行调用...这是一个错误。让它抛出异常。不要尝试通过(在本示例中)返回 null 来弥补错误。


我的问题更为笼统,不仅仅针对 null 值。

...我的回答中的大部分要点都与 null 值无关!

但请记住,在很多情况下,您希望允许偶尔出现 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() 本质上是不好的。但事实上,您觉得需要像这样编写方法,这可能表明空处理不太理想。

最后,还有其他方法可以在 API 中对“没有值”进行建模。其中包括:

  • 在 setter 和构造函数中测试意外的 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 to raw.substring(1) you would also catch potential StringIndexOutOfBoundsExceptions ... 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 of null strings, and attempt to limit their (intentional) spread. Where possible, use an empty string instead of null to mean "no value". And when you do have a case where you need to pass or return a null string, document it clearly in your method javadocs. If your methods get called with a null 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) returning null.


My question was more general, not only for null values.

... and most of the points in my answer are not about null values!

But please bear in mind that there are many cases where you want to allow the occasional null value, or any other value that could produce an exception, and just ignore them. This is the case, for example, when reading key/pair values from somewhere and passing them to a method like the tryTrim() above.

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 with null. Compare these three bits of code:

// 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();

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 the null is allowed to propagate from its point origin, the more likely it is that the programmer will forget that a null 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:

  • Test for unexpected null in setters and constructors, and either throw an exception or substitute a different value.
  • Add an is<Field>Set method ... and throwing an exception in get<Field> if the result would be null.
  • Use the Null Object Pattern.
  • Use empty strings, empty collections, zero length arrays, etc instead of null strings, collections or arrays1.
  • Using 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.

伴随着你 2024-12-29 12:02:36

使用第二个版本。当有其他替代方案可用时,切勿使用异常来控制流,因为这不是它们的用途。例外是指特殊情况。

在讨论这个主题时,不要在这里捕捉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 a NullPointerException. 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.

浮世清欢 2024-12-29 12:02:36

否则,异常抛出和捕获的速度很快(尽管 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 the Throwable.fillInStackTrace() method, or to save one exception instance and throw it repeatedly instead of always creating a new exception instance.)

梦归所梦 2024-12-29 12:02:36

至于开销,您可以自己测试:

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 值,您也捕获了异常,您是否期望发生“异常”的事情(否则捕获 NullException)?
- 你发现了这个异常,你会报告它还是吞下去?
等等 等等

编辑:请参阅我的评论,了解为什么这是一个无效的测试,但我真的不想将其留在这里。只需交换 tryTrim 和 ifTrim,我们突然得到以下结果(在我的机器上):

Try time:2043
If time:6810

无需开始解释所有这些,只需阅读 this 作为开始 - Cliff 也有一些关于整个主题的很棒的幻灯片,但我目前找不到链接。

了解 Hotspot 中异常处理的工作原理后,我相当确定在正确的测试中(没有异常的 try/catch)将是基准性能(因为没有任何开销),但是 JIT 可以通过空指针检查来玩一些技巧,然后方法调用(没有显式检查,但如果对象为 null,则捕获硬件异常),在这种情况下我们会得到相同的结果。
另外不要忘记:我们正在讨论一个容易预测的差异,如果这将是一个 CPU 周期!修剪电话的费用将是这个数字的一​​百万倍。

As far as overhead goes, you can test for yourself:

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

}

The numbers I got are:

Try time:8102
If time:1956

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):

Try time:2043
If time:6810

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文