java try 块的范围应该尽可能严格吗?
有人告诉我,使用 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);
}
}
I've been told that there is some overhead in using the Java try-catch mechanism. So, while it is necessary to put methods that throw checked exception within a try block to handle the possible exception, it is good practice performance-wise to limit the size of the try block to contain only those operations that could throw exceptions.
I'm not so sure that this is a sensible conclusion.
Consider the two implementations below of a function that processes a specified text file.
Even if it is true that the first one incurs some unnecessary overhead, I find it much easier to follow. It is less clear where exactly the exceptions come from just from looking at statements, but the comments clearly show which statements are responsible.
The second one is much longer and complicated than the first. In particular, the nice line-reading idiom of the first has to be mangled to fit the readLine
call into a try block.
What is the best practice for handling exceptions in a funcion where multiple exceptions could be thrown in its definition?
This one contains all the processing code within the try block:
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);
}
}
This one contains only the methods that throw exceptions within try blocks:
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);
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
这里的基本前提是错误:
try
块的大小对性能没有影响。性能会受到运行时实际引发异常的影响,并且与try
块的大小无关。然而,保持较小的 try 块可以带来更好的程序。
您可能会捕获异常以恢复并继续,或者您可能会捕获它们只是为了将它们报告给调用者(或通过某些 UI 报告给人类)。
在第一种情况下,可以恢复的故障通常非常具体,这会导致
try
块更小。在第二种情况下,捕获异常以便可以用另一个异常包装并重新抛出或显示给用户,小的
try
块意味着您可以更准确地知道哪个操作失败了,以及发出该呼吁的更高级别背景。这允许您创建更具体的错误报告。当然,这些准则也有例外(抱歉!)。例如,在某些情况下,非常具体的错误报告可能是安全问题。
了解
try
块对编译后的代码有何影响可能很有用。它根本不会改变编译的指令! (当然,相应的 catch 块也会这样做,因为它与任何其他代码一样。)try 块会在与该方法关联的异常表中创建一个条目。该表具有一系列源指令计数器、异常类型和目标指令。当引发异常时,将检查此表以查看是否存在具有匹配类型的条目以及包含引发异常的指令的范围。如果是,则执行分支到相应的目标编号。
需要认识到的重要一点是,除非需要,否则不会查阅此表(并且对运行性能没有影响)。 (忽略类加载过程中的一点开销。)
The basic premise here is false: the size of a
try
block makes no difference in performance. Performance is affected by actually raising exceptions at runtime, and that's independent of the size of thetry
block.However, keeping try blocks small can lead to better programs.
You might catch exceptions to recover and proceed, or you might catch them simply to report them to the caller (or to a human, via some UI).
In the first case, failures from which you can recover are often very specific, and this leads to smaller
try
blocks.In the second case, where an exception is caught so that it can be wrapped by another exception and re-thrown, or displayed to the user, small
try
blocks mean that you know more precisely which operation failed, and the higher-level context in which that call was made. This allows you to create more specific error reports.Of course, there are… exceptions (sorry!) to these guidelines. For example, in some cases very specific error reports could be a security problem.
It might be useful to know what effect a
try
block has on the compiled code. It doesn't change the compiled instructions at all! (Of course, the correspondingcatch
block does, since it's like any other code.)A
try
block creates an entry in the exception table associated with the method. This table has a range of source instructions counters, an exception type, and a destination instruction. When an exception is raised, this table is examined to see if there is an entry with a matching type, and a range that includes the instruction that raised the exception. If it does, execution branches to the corresponding destination number.The important thing to realize is that this table isn't consulted (and has no effect on running performance) unless it's needed. (Neglecting a little overhead in the loading of the class.)
绝对地。方法调用也有开销。但您不应该将所有代码放在一种方法中。
不要吹响过早的优化号角,但重点应该放在易于阅读、组织等方面。语言结构很少像系统组织和算法选择那样影响性能。
对我来说,第一个最容易阅读。
Absolutely. And there's overhead to method calls, too. But you shouldn't put all your code in one method.
Not to toot the premature optimization horn, but the focus should be on ease of reading, organization, etc. Language constructs rarely impact performance as much as system organization and choice of algorithms.
To me, the first is easiest to read.
不,你唯一应该考虑的是在哪里可以合理地处理异常以及需要回收哪些资源(使用finally)。
No. The only thing that you should be considering is where you can reasonably handle the exception and what resources you need to reclaim (with finally).
这是最糟糕的过早优化。不要这样做。
“我们应该忘记小效率,大约 97% 的情况下:过早的优化是万恶之源”- Knuth。
This is premature optimization at its worst. Don't do it.
"We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil" - Knuth.
第二种方法的好处非常小。毕竟,如果您可以成功打开文件但无法读取文件,则说明您的计算机存在严重问题。因此知道 io 异常来自 readLine() 方法很少有用。 针对不同的问题抛出不同的异常(FileNotFoundException等)
另外,正如您所知,无论如何,只要您使用“逻辑”块来限定它的范围,即一次打开、读取和关闭文件,就会 ,我会选择第一个方法。它更易于阅读,并且尤其是在处理 IO 时,try-catch 开销所使用的处理器周期(如果有的话)将是最小的。
there is very very little benefit to the 2nd method. after all if you can successfully open a file but not read from it, then there is something very wrong with your computer. thus knowing that the io exception came from the readLine() method is very rarely useful. also as you know, different exceptions are thrown for different problems anyway (FileNotFoundException, etc)
as long as you scope it with a 'logical' block, ie opening, reading, and closing a file in 1 go, i would go with the first method. it's much simpler to read and, especially when dealing with IO, the processor cycles used by the try-catch overhead would be minimal if any.
在我看来,将 try 块放在可能引发异常的特定代码周围,使其更易于阅读。您可能希望为每个错误显示不同的消息并向用户提供说明,该说明将根据错误发生的位置而有所不同。
然而,大多数人提到的性能问题与引发异常有关,而不是与 try 块本身有关。
换句话说,只要从未引发错误,try 块就不会明显影响性能。您不应该将 try 块视为另一个流程控制构造并引发错误以分支代码。这就是你想要避免的。
Putting the try blocks around the specific code that may throw an exception, makes it, in my opinion easier to read. You're likely to want to display a different message for each error and provide instructions to the user, which will be different depending on where the error occurs.
However, the performance issue that most people refer to is related to raising the exception, not to the try block itself.
In other words, as long as you never have an error raised, the try block won't noticeably affect performance. You shouldn't consider a try block just another flow control construct and raise an error to branch through your code. That's what you want to avoid.
第二种方法将生成一个编译器错误,指出
reader
可能尚未初始化。您可以通过将其初始化为null
来解决这个问题,但这仅仅意味着您可以获得 NPE,并且没有任何优势。The second method will generate a compiler error that
reader
may not have been initialized. You can get around that by initializing it tonull
, but that just means you could get an NPE, and there's no advantage to that.