异常对 Java 性能的影响是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/299068/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 12:46:11  来源:igfitidea点击:

What are the effects of exceptions on performance in Java?

javaperformanceexception

提问by John Ellinwood

Question: Is exception handling in Java actually slow?

问题:Java 中的异常处理真的很慢吗?

Conventional wisdom, as well as a lot of Google results, says that exceptional logic shouldn't be used for normal program flow in Java. Two reasons are usually given,

传统观点以及 Google 的许多结果都表明,不应将异常逻辑用于 Java 中的正常程序流。通常给出两个原因,

  1. it is really slow - even an order of magnitude slower than regular code (the reasons given vary),
  1. 它真的很慢 - 甚至比常规代码慢一个数量级(给出的原因各不相同),

and

  1. it is messy because people expect only errors to be handled in exceptional code.
  1. 它很混乱,因为人们期望在异常代码中只处理错误。

This question is about #1.

这个问题是关于#1。

As an example, this pagedescribes Java exception handling as "very slow" and relates the slowness to the creation of the exception message string - "this string is then used in creating the exception object that is thrown. This is not fast." The article Effective Exception Handling in Javasays that "the reason for this is due to the object creation aspect of exception handling, which thereby makes throwing exceptions inherently slow". Another reason out there is that the stack trace generation is what slows it down.

例如,此页面将 Java 异常处理描述为“非常慢”,并将缓慢与异常消息字符串的创建联系起来 - “然后使用此字符串创建抛出的异常对象。这并不快。” 文章有效的异常使用Java处理说:“这样做的原因是由于异常处理,这使得从而抛出异常固有的缓慢的对象创建方面”。另一个原因是堆栈跟踪生成会减慢它的速度。

My testing (using Java 1.6.0_07, Java HotSpot 10.0, on 32 bit Linux), indicates that exception handling is no slower than regular code. I tried running a method in a loop that executes some code. At the end of the method, I use a boolean to indicate whether to returnor throw. This way the actual processing is the same. I tried running the methods in different orders and averaging my test times, thinking it may have been the JVM warming up. In all my tests, the throw was at least as fast as the return, if not faster (up to 3.1% faster). I am completely open to the possibility that my tests were wrong, but I haven't seen anything out there in the way of the code sample, test comparisons, or results in the last year or two that show exception handling in Java to actually be slow.

我的测试(在 32 位 Linux 上使用 Java 1.6.0_07、Java HotSpot 10.0)表明异常处理并不比常规代码慢。我尝试在执行一些代码的循环中运行一个方法。在方法的最后,我使用一个布尔值来指示是return还是throw。这样实际的处理是一样的。我尝试以不同的顺序运行这些方法并平均我的测试时间,认为这可能是 JVM 预热。在我所有的测试中,如果不是更快(最多快 3.1%),投掷至少与返回一样快。我对我的测试错误的可能性完全持开放态度,但在过去一两年的代码示例、测试比较或结果方面,我没有看到任何表明 Java 中的异常处理实际上是错误的减缓。

What leads me down this path was an API I needed to use that threw exceptions as part of normal control logic. I wanted to correct them in their usage, but now I may not be able to. Will I instead have to praise them on their forward thinking?

引导我走这条路的是我需要使用的 API,它抛出异常作为正常控制逻辑的一部分。我想更正它们的用法,但现在我可能无法做到。相反,我是否必须赞扬他们的前瞻性思维?

In the paper Efficient Java exception handling in just-in-time compilation, the authors suggest that the presence of exception handlers alone, even if no exceptions are thrown, is enough to prevent the JIT compiler from optimizing the code properly, thus slowing it down. I haven't tested this theory yet.

Efficient Java exception processing in just-in-timecompiler 一文中,作者建议单独的异常处理程序的存在,即使没有抛出异常,也足以阻止 JIT 编译器正确优化代码,从而减慢它的速度. 我还没有测试过这个理论。

采纳答案by Mecki

It depends how exceptions are implemented. The simplest way is using setjmp and longjmp. That means all registers of the CPU are written to the stack (which already takes some time) and possibly some other data needs to be created... all this already happens in the try statement. The throw statement needs to unwind the stack and restore the values of all registers (and possible other values in the VM). So try and throw are equally slow, and that is pretty slow, however if no exception is thrown, exiting the try block takes no time whatsoever in most cases (as everything is put on the stack which cleans up automatically if the method exists).

这取决于异常是如何实现的。最简单的方法是使用 setjmp 和 longjmp。这意味着 CPU 的所有寄存器都被写入堆栈(这已经需要一些时间)并且可能需要创建一些其他数据......所有这些都已经在 try 语句中发生了。throw 语句需要展开堆栈并恢复所有寄存器的值(以及 VM 中可能的其他值)。所以 try 和 throw 同样慢,而且很慢,但是如果没有抛出异常,退出 try 块在大多数情况下都不需要任何时间(因为所有东西都放在堆栈上,如果方法存在,堆栈会自动清理)。

Sun and others recognized, that this is possibly suboptimal and of course VMs get faster and faster over the time. There is another way to implement exceptions, which makes try itself lightning fast (actually nothing happens for try at all in general - everything that needs to happen is already done when the class is loaded by the VM) and it makes throw not quite as slow. I don't know which JVM uses this new, better technique...

Sun 和其他人认识到,这可能不是最理想的,当然,随着时间的推移,VM 会变得越来越快。还有另一种实现异常的方法,它使 try 本身闪电般快速(实际上,一般来说 try 根本没有发生任何事情 - 当 VM 加载类时,所有需要发生的事情都已经完成)并且它使 throw 不那么慢. 我不知道哪个 JVM 使用这种新的、更好的技术......

...but are you writing in Java so your code later on only runs on one JVM on one specific system? Since if it may ever run on any other platform or any other JVM version (possibly of any other vendor), who says they also use the fast implementation? The fast one is more complicated than the slow one and not easily possible on all systems. You want to stay portable? Then don't rely on exceptions being fast.

...但是你是用 Java 编写的,所以你的代码以后只能在一个特定系统上的一个 JVM 上运行?因为如果它可以在任何其他平台或任何其他 JVM 版本(可能是任何其他供应商)上运行,谁说他们也使用快速实现?快速的比慢的更复杂,并且在所有系统上都不容易实现。你想保持便携吗?然后不要依赖异常快速。

It also makes a big difference what you do within a try block. If you open a try block and never call any method from within this try block, the try block will be ultra fast, as the JIT can then actually treat a throw like a simple goto. It neither needs to save stack-state nor does it need to unwind the stack if an exception is thrown (it only needs to jump to the catch handlers). However, this is not what you usually do. Usually you open a try block and then call a method that might throw an exception, right? And even if you just use the try block within your method, what kind of method will this be, that does not call any other method? Will it just calculate a number? Then what for do you need exceptions? There are much more elegant ways to regulate program flow. For pretty much anything else but simple math, you will have to call an external method and this already destroys the advantage of a local try block.

在 try 块中执行的操作也有很大的不同。如果您打开一个 try 块并且从不从该 try 块中调用任何方法,则 try 块将非常快,因为 JIT 实际上可以像简单的 goto 一样处理 throw。它既不需要保存堆栈状态,也不需要在抛出异常时展开堆栈(它只需要跳转到捕获处理程序)。但是,这不是您通常所做的。通常你打开一个 try 块,然后调用一个可能抛出异常的方法,对吧?即使你只是在你的方法中使用 try 块,这将是一种什么样的方法,不调用任何其他方法?它只会计算一个数字吗?那你为什么需要例外呢?有很多更优雅的方法来调节程序流程。除了简单的数学之外,几乎所有的东西,

See the following test code:

请看以下测试代码:

public class Test {
    int value;


    public int getValue() {
        return value;
    }

    public void reset() {
        value = 0;
    }

    // Calculates without exception
    public void method1(int i) {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            System.out.println("You'll never see this!");
        }
    }

    // Could in theory throw one, but never will
    public void method2(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            throw new Exception();
        }
    }

    // This one will regularly throw one
    public void method3(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
        // an AND operation between two integers. The size of the number plays
        // no role. AND on 32 BIT always ANDs all 32 bits
        if ((i & 0x1) == 1) {
            throw new Exception();
        }
    }

    public static void main(String[] args) {
        int i;
        long l;
        Test t = new Test();

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            t.method1(i);
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method1 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method2(i);
            } catch (Exception e) {
                System.out.println("You'll never see this!");
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method2 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method3(i);
            } catch (Exception e) {
                // Do nothing here, as we will get here
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method3 took " + l + " ms, result was " + t.getValue()
        );
    }
}

Result:

结果:

method1 took 972 ms, result was 2
method2 took 1003 ms, result was 2
method3 took 66716 ms, result was 2

The slowdown from the try block is too small to rule out confounding factors such as background processes. But the catch block killed everything and made it 66 times slower!

try 块的减速太小,无法排除诸如后台进程之类的混杂因素。但是 catch 块杀死了一切,使它慢了 66 倍!

As I said, the result will not be that bad if you put try/catch and throw all within the same method (method3), but this is a special JIT optimization I would not rely upon. And even when using this optimization, the throw is still pretty slow. So I don't know what you are trying to do here, but there is definitely a better way of doing it than using try/catch/throw.

正如我所说,如果将 try/catch 和 throw all 放在同一个方法(method3)中,结果不会那么糟糕,但这是我不会依赖的特殊 JIT 优化。即使使用这种优化,抛出仍然很慢。所以我不知道你在这里想做什么,但肯定有比使用 try/catch/throw 更好的方法。

回答by user38051

Even if throwing an exception isn't slow, it's still a bad idea to throw exceptions for normal program flow. Used this way it is analogous to a GOTO...

即使抛出异常并不慢,对于正常的程序流程抛出异常仍然是一个坏主意。以这种方式使用它类似于 GOTO ...

I guess that doesn't really answer the question though. I'd imagine that the 'conventional' wisdom of throwing exceptions being slow was true in earlier java versions (< 1.4). Creating an exception requires that the VM create the entire stack trace. A lot has changed since then in the VM to speed things up and this is likely one area that has been improved.

我想这并不能真正回答问题。我想在早期的 Java 版本(< 1.4)中,抛出异常缓慢的“传统”智慧是正确的。创建异常需要 VM 创建整个堆栈跟踪。从那时起,VM 中发生了很多变化以加快速度,这可能是一个已经改进的领域。

回答by qualbeen

Why should exceptions be any slower than normal returns?

为什么异常会比正常返回慢?

As long as you don't print the stacktrace to the terminal, save it into a file or something similar, the catch-block doesn't do any more work than other code-blocks. So, I can't imagine why "throw new my_cool_error()" should be that slow.

只要您不将堆栈跟踪打印到终端,将其保存到文件或类似的东西中,catch 块就不会比其他代码块做更多的工作。所以,我无法想象为什么“throw new my_cool_error()”会这么慢。

Good question and I'm looking forward to further information on this topic!

好问题,我期待有关此主题的更多信息!

回答by Lars Westergren

I think the first article refer to the act of traversing the call stack and creating a stack trace as being the expensive part, and while the second article doesn't say it, I think that is the most expensive part of object creation. John Rose has an article where he describes different techniques for speeding up exceptions. (Preallocating and reusing an exception, exceptions without stack traces, etc)

我认为第一篇文章将遍历调用堆栈和创建堆栈跟踪的行为称为昂贵的部分,而第二篇文章没有说,我认为这是对象创建中最昂贵的部分。John Rose 在一篇文章中描述了加速异常的不同技术。(预分配和重用异常、没有堆栈跟踪的异常等)

But still - I think this should be considered only a necessary evil, a last resort. John's reason for doing this is to emulate features in other languages which aren't (yet) available in the JVM. You should NOT get into the habit of using exceptions for control flow. Especially not for performance reasons! As you yourself mention in #2, you risk masking serious bugs in your code this way, and it will be harder to maintain for new programmers.

但仍然 - 我认为这应该被视为一种必要的邪恶,最后的手段。John 这样做的原因是模拟其他语言中(尚未)在 JVM 中可用的功能。您不应该养成使用异常进行控制流的习惯。尤其不是出于性能原因!正如您自己在 #2 中提到的那样,您可能会以这种方式掩盖代码中的严重错误,并且对于新程序员来说维护起来会更加困难。

Microbenchmarks in Java are surprisingly hard to get right (I've been told), especially when you get into JIT territory, so I really doubt that using exceptions is faster than "return" in real life. For instance, I suspect you have somewhere between 2 and 5 stack frames in your test? Now imagine your code will be invoked by a JSF component deployed by JBoss. Now you might have a stack trace which is several pages long.

Java 中的微基准测试出奇地难以正确(有人告诉我),尤其是当您进入 JIT 领域时,所以我真的怀疑在现实生活中使用异常是否比“返回”更快。例如,我怀疑您的测试中有 2 到 5 个堆栈帧?现在假设您的代码将由 JBoss 部署的 JSF 组件调用。现在您可能有一个长达几页的堆栈跟踪。

Perhaps you could post your test code?

也许您可以发布您的测试代码?

回答by Tom Hawtin - tackline

HotSpot is quite capable of removing exception code for system generated exceptions, so long as it is all inlined. However, explicitly created exception and those otherwise not removed spend a lot of time creating the stack trace. Override fillInStackTraceto see how this can affect performance.

HotSpot 非常有能力为系统生成的异常删除异常代码,只要它都是内联的。但是,显式创建的异常和未删除的异常会花费大量时间创建堆栈跟踪。覆盖fillInStackTrace以查看这如何影响性能。

回答by James Schek

I've done some performance testing with JVM 1.5 and using exceptions was at least 2x slower. On average: Execution time on a trivially small method more than tripled (3x) with exceptions. A trivially small loop that had to catch the exception saw a 2x increase in self-time.

我已经用 JVM 1.5 进行了一些性能测试,使用异常至少慢了 2 倍。平均而言:一个微不足道的小方法的执行时间超过三倍(3 倍),但有异常。一个必须捕获异常的微不足道的小循环看到自时间增加了 2 倍。

I've seen similar numbers in production code as well as micro benchmarks.

我在生产代码和微基准测试中看到了类似的数字。

Exceptions should definately NOTbe used for anything that's called frequently. Throwing a thousands of exceptions a second would cause a huge bottle neck.

异常绝对应该用于任何经常调用的东西。每秒抛出数千个异常会导致一个巨大的瓶颈。

For example, using "Integer.ParseInt(...)" to find all bad values in a very large text file--very bad idea. (I have seen this utility method killperformance on production code)

例如,使用“Integer.ParseInt(...)”在一个非常大的文本文件中查找所有错误值——这是一个非常糟糕的主意。(我看到这个工具方法生产代码的性能)

Using an exception to report a bad value on a user GUI form, probably not so bad from a performance standpoint.

使用异常报告用户 GUI 表单上的错误值,从性能的角度来看可能还不错。

Whether or not its a good design practice, I'd go with the rule: if the error is normal/expected, then use a return value. If it's abnormal, use an exception. For example: reading user inputs, bad values are normal--use an error code. Passing a value to an internal utility function, bad values should be filtered by calling code--use an exception.

无论它是否是一个好的设计实践,我都会遵循规则:如果错误是正常的/预期的,则使用返回值。如果不正常,使用异常。例如:读取用户输入,错误值是正常的——使用错误代码。将值传递给内部实用程序函数,应通过调用代码来过滤错误值——使用异常。

回答by Alan Moore

A while back I wrote a class to test the relative performance of converting strings to ints using two approaches: (1) call Integer.parseInt() and catch the exception, or (2) match the string with a regex and call parseInt() only if the match succeeds. I used the regex in the most efficient way I could (i.e., creating the Pattern and Matcher objects before intering the loop), and I didn't print or save the stacktraces from the exceptions.

不久前,我写了一个类来测试使用两种方法将字符串转换为整数的相对性能:(1)调用 Integer.parseInt() 并捕获异常,或(2)将字符串与正则表达式匹配并调用 parseInt()只有匹配成功。我以最有效的方式使用了正则表达式(即,在进入循环之前创建 Pattern 和 Matcher 对象),并且我没有打印或保存异常中的堆栈跟踪。

For a list of ten thousand strings, if they were all valid numbers the parseInt() approach was four times as fast as the regex approach. But if only 80% of the strings were valid, the regex was twice as fast as parseInt(). And if 20% were valid, meaning the exception was thrown and caught 80% of the time, the regex was about twenty times as fast as parseInt().

对于一万个字符串的列表,如果它们都是有效数字,则 parseInt() 方法的速度是正则表达式方法的四倍。但如果只有 80% 的字符串有效,则正则表达式的速度是 parseInt() 的两倍。如果 20% 是有效的,这意味着在 80% 的时间内抛出并捕获了异常,则正则表达式的速度大约是 parseInt() 的 20 倍。

I was surprised by the result, considering that the regex approach processes valid strings twice: once for the match and again for parseInt(). But throwing and catching exceptions more than made up for that. This kind of situation isn't likely to occur very often in the real world, but if it does, you definitely should not use the exception-catching technique. But if you're only validating user input or something like that, by all means use the parseInt() approach.

我对结果感到惊讶,考虑到正则表达式方法处理有效字符串两次:一次用于匹配,一次用于 parseInt()。但是抛出和捕获异常足以弥补这一点。这种情况在现实世界中不太可能经常发生,但如果发生了,你绝对不应该使用异常捕获技术。但是如果你只是验证用户输入或类似的东西,一定要使用 parseInt() 方法。

回答by incarnate

Don't know if these topics relate, but I once wanted to implement one trick relying on current thread's stack trace: I wanted to discover the name of the method, which triggered the instantiation inside the instantiated class (yeap, the idea is crazy, I totally gave it up). So I discovered that calling Thread.currentThread().getStackTrace()is extremelyslow (due to native dumpThreadsmethod which it uses internally).

不知道这些主题是否相关,但我曾经想通过当前线程的堆栈跟踪来实现一个技巧:我想发现方法的名称,它触发了实例化类内部的实例化(是的,这个想法很疯狂,我完全放弃了)。所以我发现,调用Thread.currentThread().getStackTrace()极其缓慢的(由于本机dumpThreads供内部使用的方法)。

So Java Throwable, correspondingly, has a native method fillInStackTrace. I think that the killer-catchblock described earlier somehow triggers the execution of this method.

因此,JavaThrowable相应地具有本机方法fillInStackTrace。我认为catch前面描述的杀手块以某种方式触发了这个方法的执行。

But let me tell you another story...

但是让我告诉你另一个故事......

In Scala some functional features are compiled in JVM using ControlThrowable, which extends Throwableand overrides its fillInStackTracein a following way:

在 Scala 中,一些功能特性是在 JVM 中使用 编译的,它以以下方式ControlThrowable扩展Throwable和覆盖它fillInStackTrace

override def fillInStackTrace(): Throwable = this

So I adapted the test above (cycles amount are decreased by ten, my machine is a bit slower :):

所以我调整了上面的测试(周期数减少了十个,我的机器有点慢:):

class ControlException extends ControlThrowable

class T {
  var value = 0

  def reset = {
    value = 0
  }

  def method1(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      println("You'll never see this!")
    }
  }

  def method2(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      throw new Exception()
    }
  }

  def method3(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new Exception()
    }
  }

  def method4(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new ControlException()
    }
  }
}

class Main {
  var l = System.currentTimeMillis
  val t = new T
  for (i <- 1 to 10000000)
    t.method1(i)
  l = System.currentTimeMillis - l
  println("method1 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method2(i)
  } catch {
    case _ => println("You'll never see this")
  }
  l = System.currentTimeMillis - l
  println("method2 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method4(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method4 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method3(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method3 took " + l + " ms, result was " + t.value)

}

So, the results are:

所以,结果是:

method1 took 146 ms, result was 2
method2 took 159 ms, result was 2
method4 took 1551 ms, result was 2
method3 took 42492 ms, result was 2

You see, the only difference between method3and method4is that they throw different kinds of exceptions. Yeap, method4is still slower than method1and method2, but the difference is far more acceptable.

你看,method3和之间的唯一区别method4是它们抛出不同类型的异常。叶氏,method4仍慢于method1method2,但不同的是远更容易接受。

回答by inflamer

Just compare let's say Integer.parseInt to the following method, which just returns a default value in the case of unparseable data instead of throwing an Exception:

只需将 Integer.parseInt 与以下方法进行比较,该方法仅在无法解析数据的情况下返回默认值,而不是抛出异常:

  public static int parseUnsignedInt(String s, int defaultValue) {
    final int strLength = s.length();
    if (strLength == 0)
      return defaultValue;
    int value = 0;
    for (int i=strLength-1; i>=0; i--) {
      int c = s.charAt(i);
      if (c > 47 && c < 58) {
        c -= 48;
        for (int j=strLength-i; j!=1; j--)
          c *= 10;
        value += c;
      } else {
        return defaultValue;
      }
    }
    return value < 0 ? /* übergebener wert > Integer.MAX_VALUE? */ defaultValue : value;
  }

As long as you apply both methods to "valid" data, they both will work at approximately the same rate (even although Integer.parseInt manages to handle more complex data). But as soon as you try to parse invalid data (e.g. to parse "abc" 1.000.000 times), the difference in performance should be essential.

只要您将这两种方法应用于“有效”数据,它们就会以大致相同的速率工作(即使 Integer.parseInt 设法处理更复杂的数据)。但是一旦您尝试解析无效数据(例如解析“abc” 1.000.000 次),性能差异就很重要。

回答by Fuwjax

My answer, unfortunately, is just too long to post here. So let me summarize here and refer you to http://www.fuwjax.com/how-slow-are-java-exceptions/for the gritty details.

不幸的是,我的回答太长了,无法在这里发布。所以让我在这里总结一下,并请您访问http://www.fuwjax.com/how-slow-are-java-exceptions/以了解详细信息。

The real question here is not "How slow are 'failures reported as exceptions' compared to 'code that never fails'?" as the accepted response might have you believe. Instead, the question should be "How slow are 'failures reported as exceptions' compared to failures reported other ways?" Generally, the two other ways of reporting failures are either with sentinel values or with result wrappers.

这里真正的问题不是“与‘永不失败的代码’相比,‘作为异常报告的失败’有多慢?” 因为接受的回应可能会让你相信。相反,问题应该是“与以其他方式报告的失败相比,‘将失败报告为异常’的速度有多慢?” 通常,报告失败的另外两种方式是使用标记值或使用结果包装器。

Sentinel values are an attempt to return one class in the case of success and another in the case of failure. You can think of it almost as returning an exception instead of throwing one. This requires a shared parent class with the success object and then doing an "instanceof" check and a couple casts to get the success or failure information.

哨兵值试图在成功的情况下返回一个类,在失败的情况下返回另一个类。您几乎可以将其视为返回异常而不是抛出异常。这需要一个具有成功对象的共享父类,然后进行“instanceof”检查和几次强制转换以获取成功或失败信息。

It turns out that at the risk of type safety, Sentinel values are faster than exceptions, but only by a factor of roughly 2x. Now, that may seem like a lot, but that 2x only covers the cost of the implementation difference. In practice, the factor is much lower since our methods that might fail are much more interesting than a few arithmetic operators as in the sample code elsewhere in this page.

事实证明,冒着类型安全的风险,Sentinel 值比异常快,但只有大约 2 倍。现在,这可能看起来很多,但 2x 仅涵盖了实现差异的成本。实际上,这个因素要低得多,因为我们可能会失败的方法比本页其他地方的示例代码中的一些算术运算符有趣得多。

Result Wrappers, on the other hand, do not sacrifice type safety at all. They wrap the success and failure information in a single class. So instead of "instanceof" they provide an "isSuccess()" and getters for both the success and failure objects. However, result objects are roughly 2x slowerthan using exceptions. It turns out that creating a new wrapper object every time is much more expensive than throwing an exception sometimes.

另一方面,结果包装器根本不会牺牲类型安全性。它们将成功和失败信息包装在一个类中。因此,它们不是“instanceof”,而是为成功和失败对象提供“isSuccess()”和getter。但是,结果对象大约比使用异常2。事实证明,每次创建一个新的包装器对象比有时抛出异常要昂贵得多。

On top of that, exceptions are the language supplied the way of indicating that a method might fail. There's no other way to tell from just the API which methods are expected to always (mostly) work and which are expected to report failure.

最重要的是,异常是语言提供的指示方法可能失败的方式。没有其他方法可以仅从 API 中判断哪些方法预计始终(大部分)有效,哪些预计报告失败。

Exceptions are safer than sentinels, faster than result objects, and less surprising than either. I'm not suggesting that try/catch replace if/else, but exceptions are the right way to report failure, even in the business logic.

异常比哨兵更安全,比结果对象更快,也没有任何一个令人惊讶。我并不是建议 try/catch 替换 if/else,但异常是报告失败的正确方式,即使在业务逻辑中也是如此。

That said, I would like to point out that the two most frequent ways of substantially impacting performance I've run across are creating unnecessary objects and nested loops. If you have a choice between creating an exception or not creating an exception, don't create the exception. If you have a choice between creating an exception sometimes or creating another object all the time, then create the exception.

也就是说,我想指出,我遇到过的对性能产生重大影响的两种最常见的方式是创建不必要的对象和嵌套循环。如果您可以选择创建异常或不创建异常,请不要创建异常。如果您可以在有时创建异常或始终创建另一个对象之间做出选择,则创建异常。