应该尝试...catch 进入循环内部还是外部?
Posted
技术标签:
【中文标题】应该尝试...catch 进入循环内部还是外部?【英文标题】:Should try...catch go inside or outside a loop? 【发布时间】:2010-09-13 13:59:24 【问题描述】:我有一个看起来像这样的循环:
for (int i = 0; i < max; i++)
String myString = ...;
float myNum = Float.parseFloat(myString);
myFloats[i] = myNum;
这是一个方法的主要内容,其唯一目的是返回浮点数组。如果出现错误,我希望此方法返回 null
,因此我将循环放在 try...catch
块中,如下所示:
try
for (int i = 0; i < max; i++)
String myString = ...;
float myNum = Float.parseFloat(myString);
myFloats[i] = myNum;
catch (NumberFormatException ex)
return null;
但后来我也想到将try...catch
块放入循环中,如下所示:
for (int i = 0; i < max; i++)
String myString = ...;
try
float myNum = Float.parseFloat(myString);
catch (NumberFormatException ex)
return null;
myFloats[i] = myNum;
是否有任何理由(性能或其他方面)偏爱一个而不是另一个?
编辑: 共识似乎是将循环放在 try/catch 中更简洁,可能放在它自己的方法中。但是,仍然存在关于哪个更快的争论。有人可以对此进行测试并给出统一的答案吗?
【问题讨论】:
我不知道性能如何,但是代码看起来更干净,在 for 循环之外使用 try catch。 【参考方案1】:在您的示例中没有功能差异。我发现您的第一个示例更具可读性。
【讨论】:
【参考方案2】:性能:正如Jeffrey 在他的回复中所说,在Java 中没有太大区别。
通常,为了代码的可读性,您选择捕获异常的位置取决于您是否希望循环继续处理。
在您的示例中,您在捕获异常时返回。在这种情况下,我会将 try/catch 放在循环中。如果你只是想捕捉一个坏值但继续处理,把它放在里面。
第三种方式:您始终可以编写自己的静态 ParseFloat 方法,并在该方法而不是循环中处理异常处理。使异常处理与循环本身隔离!
class Parsing
public static Float MyParseFloat(string inputValue)
try
return Float.parseFloat(inputValue);
catch ( NumberFormatException e )
return null;
// .... your code
for(int i = 0; i < max; i++)
String myString = ...;
Float myNum = Parsing.MyParseFloat(myString);
if ( myNum == null ) return;
myFloats[i] = (float) myNum;
【讨论】:
仔细看看。他在两者的第一个例外中退出。一开始我也是这么想的。 我知道,这就是我在他的情况下说的原因,因为当他遇到问题时他会回来,那么我会使用外部捕获。为清楚起见,这就是我要使用的规则... 不错的代码,但不幸的是Java没有输出参数的奢侈。 ByRef?很久没接触Java了! 其他人建议 TryParse 在 C#/.net 中允许您在不处理异常的情况下进行安全解析,现在已复制并且该方法可重用。至于最初的问题,我更喜欢 catch 内的循环,即使没有性能问题,它看起来也更干净..【参考方案3】:您应该更喜欢外部版本而不是内部版本。这只是规则的特定版本,将任何可以移出循环的内容移出循环。根据 IL 编译器和 JIT 编译器,您的两个版本可能会或可能不会以不同的性能特征结束。
另一方面,您可能应该查看 float.TryParse 或 Convert.ToFloat。
【讨论】:
【参考方案4】:如果它在内部,那么您将获得 N 次 try/catch 结构的开销,而不仅仅是外部一次。
每次调用 Try/Catch 结构都会增加方法执行的开销。只需一点点内存和处理器的滴答声就可以处理该结构。如果您正在运行一个循环 100 次,并且出于假设的原因,假设成本是每次 try/catch 调用的 1 个滴答声,那么在循环中使用 Try/Catch 会花费您 100 个滴答声,而如果是,则只需 1 个滴答声在循环之外。
【讨论】:
您能详细说明一下开销吗? 好的,但是 Jeffrey L Whitledge 说没有没有额外的开销。你能提供一个说有的来源吗? 成本很小,几乎可以忽略不计,但它就在那里——一切都有成本。这是 Programmer's Heaven (tiny.cc/ZBX1b) 关于 .NET 的博客文章,这是 Reshat Sabiq 关于 JAVA (tiny.cc/BV2hS) 的新闻组文章 我明白了...所以现在的问题是,进入 try/catch 所产生的成本(在这种情况下应该在循环之外),还是在try/catch(在这种情况下它应该在循环内)?你说它是#1。而且我仍然不完全相信这是成本。 根据这本 Google Book (tinyurl.com/3pp7qj),“最新的虚拟机没有任何惩罚”。【参考方案5】:如果您将 try/catch 放入循环中,您将在异常后继续循环。如果你把它放在循环之外,一旦抛出异常,你就会停止。
【讨论】:
好吧,我在异常时返回 null,所以在我的情况下它不会继续处理。 至少在python中,这取决于异常返回的内容。如果异常引发非零退出,它将退出循环。【参考方案6】:如果全有或全无失败,那么第一种格式是有意义的。如果您希望能够处理/返回所有非失败元素,则需要使用第二种形式。这些将是我在这些方法之间进行选择的基本标准。就个人而言,如果是全有或全无,我不会使用第二种形式。
【讨论】:
【参考方案7】:为 try/catch 设置一个特殊的堆栈帧会增加额外的开销,但 JVM 可能能够检测到您正在返回的事实并对其进行优化。
根据迭代次数,性能差异可能可以忽略不计。
但是我同意其他人的观点,将它放在循环之外会使循环体看起来更干净。
如果您有可能想要继续处理而不是在出现无效数字时退出,那么您会希望代码位于循环内。
【讨论】:
【参考方案8】:性能:
try/catch 结构的放置位置绝对没有性能差异。在内部,它们被实现为结构中的代码范围表,该结构在调用方法时创建。当方法执行时,try/catch 结构完全不在图片中,除非发生 throw,然后将错误的位置与表进行比较。
这是一个参考:http://www.javaworld.com/javaworld/jw-01-1997/jw-01-hood.html
桌子被描述到一半。
【讨论】:
好吧,你是说除了美学没有区别。你能链接到一个来源吗? 谢谢。我想自 1997 年以来情况可能发生了变化,但它们几乎不会使其降低效率。 绝对是一个艰难的词。在某些情况下,它会抑制编译器优化。不是直接成本,但可能是间接的性能损失。 没错,我想我应该稍微控制一下我的热情,但错误信息让我很紧张!在优化的情况下,由于我们不知道性能会受到何种影响,我想还是回到了尝试和测试(一如既往)。 没有性能差异,只有代码运行无异常。【参考方案9】:例外的全部意义在于鼓励第一种风格:让错误处理合并并处理一次,而不是立即在每个可能的错误位置处理。
【讨论】:
错了!异常的意义在于确定在发生“错误”时采用何种异常行为。因此选择内部或外部 try/catch 块实际上取决于您要如何处理循环处理。 通过预异常错误处理同样可以做到这一点,例如传递“发生错误”标志。【参考方案10】:好吧,在Jeffrey L Whitledge said 没有性能差异之后(截至 1997 年),我去测试了它。我运行了这个小基准测试:
public class Main
private static final int NUM_TESTS = 100;
private static int ITERATIONS = 1000000;
// time counters
private static long inTime = 0L;
private static long aroundTime = 0L;
public static void main(String[] args)
for (int i = 0; i < NUM_TESTS; i++)
test();
ITERATIONS += 1; // so the tests don't always return the same number
System.out.println("Inside loop: " + (inTime/1000000.0) + " ms.");
System.out.println("Around loop: " + (aroundTime/1000000.0) + " ms.");
public static void test()
aroundTime += testAround();
inTime += testIn();
public static long testIn()
long start = System.nanoTime();
Integer i = tryInLoop();
long ret = System.nanoTime() - start;
System.out.println(i); // don't optimize it away
return ret;
public static long testAround()
long start = System.nanoTime();
Integer i = tryAroundLoop();
long ret = System.nanoTime() - start;
System.out.println(i); // don't optimize it away
return ret;
public static Integer tryInLoop()
int count = 0;
for (int i = 0; i < ITERATIONS; i++)
try
count = Integer.parseInt(Integer.toString(count)) + 1;
catch (NumberFormatException ex)
return null;
return count;
public static Integer tryAroundLoop()
int count = 0;
try
for (int i = 0; i < ITERATIONS; i++)
count = Integer.parseInt(Integer.toString(count)) + 1;
return count;
catch (NumberFormatException ex)
return null;
我使用 javap 检查了生成的字节码,以确保没有内联。
结果表明,假设 JIT 优化不显着,Jeffrey 是正确的;绝对在 Java 6、Sun 客户端 VM 上没有性能差异(我无法访问其他版本)。整个测试的总时间差大约为几毫秒。
因此,唯一的考虑是看起来最干净的东西。我发现第二种方式很丑,所以我会坚持第一种方式或Ray Hayes's way。
【讨论】:
如果异常处理程序将流程置于循环之外,那么我觉得将它放在 outside 循环中是最清楚的——而不是显然将 catch 子句放在循环中,尽管如此,它仍然返回。我在这种“catch-all”方法的“caught”主体周围使用了一行空格,以便更清楚地将 body 与 all-enclosure try 块分开。 【参考方案11】:我同意所有关于性能和可读性的帖子。但是,在某些情况下它确实很重要。其他几个人提到了这一点,但通过示例可能更容易看到。
考虑这个稍作修改的例子:
public static void main(String[] args)
String[] myNumberStrings = new String[] "1.2345", "asdf", "2.3456";
ArrayList asNumbers = parseAll(myNumberStrings);
public static ArrayList parseAll(String[] numberStrings)
ArrayList myFloats = new ArrayList();
for(int i = 0; i < numberStrings.length; i++)
myFloats.add(new Float(numberStrings[i]));
return myFloats;
如果您希望 parseAll() 方法在有任何错误时返回 null(如原始示例),您可以像这样将 try/catch 放在外面:
public static ArrayList parseAll1(String[] numberStrings)
ArrayList myFloats = new ArrayList();
try
for(int i = 0; i < numberStrings.length; i++)
myFloats.add(new Float(numberStrings[i]));
catch (NumberFormatException nfe)
//fail on any error
return null;
return myFloats;
实际上,您可能应该在此处返回错误而不是 null,而且通常我不喜欢多次返回,但您明白了。
另一方面,如果您希望它忽略问题,并解析它可以解析的任何字符串,您可以将 try/catch 放在循环内部,如下所示:
public static ArrayList parseAll2(String[] numberStrings)
ArrayList myFloats = new ArrayList();
for(int i = 0; i < numberStrings.length; i++)
try
myFloats.add(new Float(numberStrings[i]));
catch (NumberFormatException nfe)
//don't add just this one
return myFloats;
【讨论】:
这就是为什么我说“如果你只是想捕捉一个坏值但继续处理,把它放进去。”【参考方案12】:把它放在里面。您可以继续处理(如果需要),或者您可以抛出一个有用的异常,告诉客户端 myString 的值和包含错误值的数组的索引。我认为 NumberFormatException 已经告诉你错误的值,但原则是将所有有用的数据放在你抛出的异常中。想想在程序的这一点上调试器中你会感兴趣的是什么。
考虑:
try
// parse
catch (NumberFormatException nfe)
throw new RuntimeException("Could not parse as a Float: [" + myString +
"] found at index: " + i, nfe);
在需要时,您会非常感谢这样的异常,其中包含尽可能多的信息。
【讨论】:
是的,这也是一个有效的观点。就我而言,我正在从文件中读取,所以我真正需要的是无法解析的字符串。所以我做了 System.out.println(ex) 而不是抛出另一个异常(我想解析尽可能多的文件)。【参考方案13】:我想添加我自己的0.02c
关于在查看异常处理的一般问题时的两个相互竞争的考虑:
try-catch
块的“更广泛”职责(即在您的情况下位于循环之外)意味着在稍后更改代码时,您可能会错误地添加由现有catch
块;可能是无意的。在您的情况下,这不太可能,因为您明确捕获了 NumberFormatException
try-catch
块的职责“越窄”,重构就越困难。特别是当(如您的情况)您从 catch
块(return null
语句)中执行“非本地”指令时。
【讨论】:
【参考方案14】:这取决于故障处理。如果您只想跳过错误元素,请在里面尝试:
for(int i = 0; i < max; i++)
String myString = ...;
try
float myNum = Float.parseFloat(myString);
myFloats[i] = myNum;
catch (NumberFormatException ex)
--i;
在任何其他情况下,我更喜欢在外面尝试。代码更易读,更干净。如果返回 null,也许在错误情况下抛出 IllegalArgumentException 会更好。
【讨论】:
【参考方案15】:我会投入我的 0.02 美元。有时您最终需要稍后在代码中添加“finally”(因为谁第一次就完美地编写了他们的代码?)。在这些情况下,突然间将 try/catch 放在循环之外更有意义。例如:
try
for(int i = 0; i < max; i++)
String myString = ...;
float myNum = Float.parseFloat(myString);
dbConnection.update("MY_FLOATS","INDEX",i,"VALUE",myNum);
catch (NumberFormatException ex)
return null;
finally
dbConnection.release(); // Always release DB connection, even if transaction fails.
因为无论是否出现错误,您都只想释放一次数据库连接(或选择您最喜欢的其他资源类型...)。
【讨论】:
【参考方案16】:如前所述,性能是相同的。但是,用户体验不一定相同。在第一种情况下,您会很快失败(即在第一个错误之后),但是如果您将 try/catch 块放在循环中,您可以捕获为给定的方法调用创建的所有错误。从字符串中解析值数组时,您希望出现一些格式错误,在某些情况下,您肯定希望能够将所有错误呈现给用户,这样他们就不需要一个一个地尝试修复它们.
【讨论】:
对于这种特殊情况并非如此(我将在 catch 块中返回),但总的来说,记住这一点是件好事。【参考方案17】:上面没有提到的另一个方面是每个 try-catch 对堆栈都有一些影响,这可能对递归方法产生影响。
如果方法“outer()”调用方法“inner()”(可能会递归调用自身),如果可能,尝试在方法“outer()”中定位try-catch。我们在性能类中使用的一个简单的“堆栈崩溃”示例在 try-catch 位于内部方法中时在大约 6,400 帧时失败,而在外部方法中时在大约 11,600 帧处失败。
在现实世界中,如果您使用的是复合模式并且具有大型复杂的嵌套结构,这可能会成为一个问题。
【讨论】:
【参考方案18】:只要你知道你需要在循环中完成什么,你就可以将 try catch 放在循环之外。但重要的是要了解,一旦发生异常,循环就会结束,这可能并不总是你想要的。这实际上是基于 Java 的软件中非常常见的错误。人们需要处理许多项目,例如清空队列,并错误地依赖外部 try/catch 语句来处理所有可能的异常。他们也可能只处理循环内的特定异常,而不期望发生任何其他异常。 然后,如果发生了未在循环内部处理的异常,则循环将被“抢占”,它可能会提前结束,并且外部 catch 语句会处理该异常。
如果循环在生活中的角色是清空队列,那么该循环很可能会在队列真正清空之前结束。很常见的故障。
【讨论】:
【参考方案19】:虽然性能可能相同并且“看起来”更好是非常主观的,但功能上仍然存在很大差异。举个例子:
Integer j = 0;
try
while (true)
++j;
if (j == 20) throw new Exception();
if (j%4 == 0) System.out.println(j);
if (j == 40) break;
catch (Exception e)
System.out.println("in catch block");
while 循环在 try catch 块内,变量 'j' 递增直到达到 40,当 j mod 4 为零时打印出来,当 j 达到 20 时抛出异常。
在任何细节之前,这里是另一个例子:
Integer i = 0;
while (true)
try
++i;
if (i == 20) throw new Exception();
if (i%4 == 0) System.out.println(i);
if (i == 40) break;
catch (Exception e) System.out.println("in catch block");
与上面的逻辑相同,唯一的区别是 try/catch 块现在位于 while 循环内。
输出如下(在 try/catch 中):
4
8
12
16
in catch block
还有另一个输出(try/catch in while):
4
8
12
16
in catch block
24
28
32
36
40
你有很大的不同:
while in try/catch 跳出循环
try/catch in while 保持循环处于活动状态
【讨论】:
因此,如果循环被视为单个“单元”并且在该单元中发现问题会使整个“单元”(或本例中的循环)向南移动,请将try catch()
放在循环周围。如果您将循环的单个迭代或数组中的单个元素视为一个整体“单元”,请将 try catch()
放入循环中。如果该“单元”中发生异常,我们只需跳过该元素,然后继续循环的下一次迭代。【参考方案20】:
如果您想为每次迭代捕获异常,或者检查在哪个迭代抛出异常并捕获迭代中的每个异常,请将 try...catch 放在循环中。如果发生异常,这不会中断循环,并且您可以在整个循环的每次迭代中捕获每个异常。
如果您想中断循环并在抛出异常时检查异常,请使用 try...catch 退出循环。这将打破循环并在 catch 之后执行语句(如果有的话)。
这一切都取决于您的需要。我更喜欢在部署时在循环内使用 try...catch,因为如果发生异常,结果不会有歧义,循环不会中断并完全执行。
【讨论】:
【参考方案21】:我的观点是 try/catch 块对于确保正确的异常处理是必要的,但是创建这样的块会影响性能。由于循环包含密集的重复计算,因此不建议将 try/catch 块放入循环中。此外,似乎发生这种情况的地方,通常是“异常”或“运行时异常”被捕获。应避免在代码中捕获 RuntimeException。同样,如果您在一家大公司工作,则必须正确记录该异常,或阻止运行时异常发生。这段描述的重点是PLEASE AVOID USING TRY-CATCH BLOCKS IN LOOPS
【讨论】:
以上是关于应该尝试...catch 进入循环内部还是外部?的主要内容,如果未能解决你的问题,请参考以下文章