Java中的try块是否应尽可能紧密地限定作用域?

42
我听说使用Java的try-catch机制有一定的开销。因此,虽然需要将抛出已检查异常的方法放在try块中以处理可能的异常,但从性能的角度来看,最好限制try块的大小,只包含可能引发异常的那些操作。
我不确定这是否是一个明智的结论。
考虑下面两个实现文件处理函数的示例。
即使第一个实现确实产生了一些不必要的开销,但我认为它更易于理解。仅从语句中看不清楚异常的确切来源,但注释清楚地显示哪些语句是有问题的。
第二个比第一个要长得多且复杂。特别是,第一个的良好行读取方式必须改整以适应readLine调用的try块。
在定义中可能会引发多个异常的函数中处理异常的最佳实践是什么?
以下示例将所有处理代码都包含在try块中:
void processFile(File f)
{
  try
  {
    // construction of FileReader can throw FileNotFoundException
    BufferedReader in = new BufferedReader(new FileReader(f));

    // call of readLine can throw IOException
    String line;
    while ((line = in.readLine()) != null)
    {
      process(line);
    }
  }
  catch (FileNotFoundException ex)
  {
    handle(ex);
  }
  catch (IOException ex)
  {
    handle(ex);
  }
}

这个只包含在try块中抛出异常的方法:

void processFile(File f)
{
  FileReader reader;
  try
  {
    reader = new FileReader(f);
  }
  catch (FileNotFoundException ex)
  {
    handle(ex);
    return;
  }

  BufferedReader in = new BufferedReader(reader);

  String line;
  while (true)
  {
    try
    {
      line = in.readLine();
    }
    catch (IOException ex)
    {
      handle(ex);
      break;
    }

    if (line == null)
    {
      break;
    }

    process(line);
  }
}

10
“过早优化”是一个术语,用于描述程序员让性能考虑影响代码设计的情况。这可能会导致设计不够简洁或者代码不正确,因为优化使代码变得复杂,而程序员则被优化所分心。-维基百科 [http://en.wikipedia.org/wiki/Program_optimization#When_to_optimize] - Bert F
2
我们应该忘记小的效率问题,大约97%的时间:过早优化是万恶之源。然而,在关键的3%中,我们不应该放弃机会。- Knuth [http://stackoverflow.com/questions/211414/is-premature-optimization-really-the-root-of-all-evil] - Bert F
过早的优化是所有科学的根源。 - Dmytro
7个回答

50
基本前提是错误的:`try`块的大小对性能没有影响。性能受实际运行时抛出异常的影响,这与`try`块的大小无关。
然而,保持`try`块小可能会导致更好的程序。
您可能会捕获异常来恢复和继续执行,或者仅仅是为了将其报告给调用者(或通过某些用户界面报告给人类)。
在第一种情况下,您可以恢复的故障通常非常特定,这导致较小的`try`块。
在第二种情况下,捕获异常以便将其包装在另一个异常中并重新抛出,或者显示给用户,较小的`try`块意味着您更精确地知道哪个操作失败,以及进行该调用的高级上下文。这允许您创建更具体的错误报告。
当然,这些准则也有例外情况(抱歉!)。例如,在某些情况下,非常特定的错误报告可能会带来安全问题。
了解`try`块对编译代码有什么影响可能很有用。它根本不会改变编译指令! (当然,相应的`catch`块会改变,因为它像任何其他代码一样。)
`try`块会为该方法创建异常表中的一个条目。此表具有一系列源指令计数器、异常类型和目标指令。当引发异常时,将检查该表以查看是否存在具有匹配类型和包括引发异常的指令的范围的条目。如果是,则执行分支到相应的目标号。
重要的是要意识到,除非需要,否则不会查询此表(并且对运行性能没有影响)。 (忽略类加载中的一些开销。)

7
没错。值得注意的是,在Java虚拟机规范中详细说明,try-catch语句的额外开销只在抛出异常或块中有finally子句时才会发生。http://java.sun.com/docs/books/jvms/second_edition/html/Compiling.doc.html#9934 - ig0774
1
@ig0774 所以,如果我添加一个像 finally { in.close(); } 这样的 finally 块,那么相关的开销就会生效吗?根据我对规范的理解,finally 子句只是在编译 try 块时添加了一个额外的指令,以在正常返回之前调用 finally 块作为子例程。 - Iain Samuel McLean Elder
1
@isme:我所说的“开销”指的是finally块中的单个指令;它与try {}块不同,后者不会添加任何指令或特殊处理。 - ig0774
因此,try-catch-finally机制产生的开销量与finally块的数量成正比。而且最多只能有一个finally块。因此,在最坏的情况下,JVM会执行一个额外的指令,该指令是“执行finally块”。特别地,开销量与try块的大小无关!感谢erickson和ig0774让我明白这一点并简单明了地解释了它! - Iain Samuel McLean Elder

12

有人告诉我,使用Java的try-catch机制会增加一些开销。

完全正确。而且方法调用也会有开销。但是你不应该把所有的代码都放在一个方法中。

并不是要过分关注优化,而是应该关注代码的易读性、组织性等。语言结构很少对性能产生影响,系统的组织方式和算法选择才是更重要的。

对我来说,第一种方式最易读。


3
不需要。你需要考虑的唯一事情是在哪里可以合理地处理异常以及需要回收哪些资源(使用finally)。

在编程中,如果提到清理资源,那么就要加上+1分。在原始帖子中,只有第一个实现可以添加一个单独的“finally”子句来关闭文件。 - Kevin Brock
+1 提到清理。直到现在,我从未对我使用的任何“Reader”对象调用过“close”。真是丢人! - Iain Samuel McLean Elder
Java 7 的 try-with-resources 对您的偏好有多大影响? - Eric Jablow
我非常支持将try-catch和try-finally分开,它们有两个不同的目的,应该分别处理。因此,我认为try-with-resources虽然更易于阅读,但并不影响代码结构。 - CurtainDog

2
这是最糟糕的过早优化。不要这样做。
“我们应该忘记小优化,大约有97%的时间:过早优化是万恶之源”- Knuth。

1

第二种方法的好处非常非常少。毕竟,如果您可以成功打开文件但无法从中读取数据,那么您的计算机就出了一些问题。因此,知道io异常来自readLine()方法很少会有用。而且正如您所知,不同的问题会抛出不同的异常(FileNotFoundException等)

只要您将其范围限定在“逻辑”块内,即在一起打开、读取和关闭文件,我会选择第一种方法。它更容易阅读,并且尤其是在处理IO时,try-catch开销使用的处理器周期最小(如果有的话)。


1
将try块放在可能引发异常的特定代码周围,我认为这样更容易阅读。您可能希望为每个错误显示不同的消息并向用户提供说明,这取决于错误发生的位置。
然而,大多数人提到的性能问题与引发异常有关,而不是try块本身。
换句话说,只要您从未引发错误,try块就不会明显影响性能。您不应将try块视为另一种流程控制结构,并引发错误以分支执行代码。这是您要避免的。

2
关于你最后一段的补充说明。如果你在循环内部有一个try/catch,你可以捕获异常并继续循环。如果你的try/catch在循环外部,你的循环将被中止。任何一种情况都可能是你想要的,并且会影响你放置try/catch的位置。 - Jonathon Faust

0
第二种方法将生成编译器错误,指出可能尚未初始化reader。您可以通过将其初始化为null来解决这个问题,但这只意味着您可能会得到NPE,并且没有任何优势。

不会产生这样的编译器错误。catch 块末尾的 return 语句确保了 reader 将被初始化或永远不会被读取!当然,在编译器发出您描述的错误后,我才添加了此语句。 :) - Iain Samuel McLean Elder
确实如此 - 我错过了那个。谢天谢地编译器在这里为我们解决这些问题。 :) - Matt McHenry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接