应该尝试…赶上循环内部或外部?

我有一个循环,看起来像这样:

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

有没有任何理由,performance或其他,比较喜欢一个呢?


编辑:共识似乎是把它放在try / catch里面,可能在它自己的方法里面。 但是,还有哪些更快的争论。 有人可以testing这个,回来一个统一的答案?

性能:

try / catch结构的放置位置绝对没有性能差异。 在内部,它们被实现为在调用方法时创build的结构中的代码范围表。 当该方法正在执行时,try / catch结构完全不在图片中,除非发生了抛出,然后将错误的位置与表进行比较。

这里有一个参考: http : //www.javaworld.com/javaworld/jw-01-1997/jw-01-hood.html

表格是关于中途描述的。

performance :正如Jeffrey在答复中所说的那样,在Java中并没有太大的区别。

一般来说 ,为了代码的可读性,你在哪里捕捉exception的select取决于你是否希望循环继续处理。

在你的例子中,你捕获exception时返回。 在这种情况下,我会把try / catch放在循环中。 如果你只是想抓住一个不好的价值,但进行处理,把它放进去。

第三种方法 :你总是可以编写自己的静态ParseFloat方法,并在该方法中处理exception处理而不是循环。 使exception处理独立于循环本身!

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

好吧,在Jeffrey L Whitledge说没有performance差异(截至1997年)之后,我去testing了它。 我跑了这个小基准:

 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 (我没有访问其他版本)绝对没有性能差异 。 在整个testing中,总时间差约为几毫秒。

因此,唯一的考虑是看起来最干净的。 我发现第二种方法很难看,所以我会坚持第一种方式或雷·海耶斯的方式 。

我同意所有的performance和可读性的职位。 但是,有些情况确实很重要。 其他人提到这一点,但用例子可能会更容易看出来。

考虑这个稍微修改的例子:

 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,通常我不喜欢有多个返回,但是你明白了。

另一方面,如果你想让它忽略这些问题,并且parsing它的任何string,你可以把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; } 

如前所述,性能是一样的。 但是,用户体验不一定完全相同。 在第一种情况下,你会快速失败(即在第一个错误之后),但是如果你把try / catch块放在循环中,你可以捕获给定的调用方法创build的所有错误。 从string中分析值的数组时,如果您希望发生某些格式错误,则肯定会出现所有错误提示给用户的情况,这样他们就不需要逐一修复它们。

虽然性能可能是相同的,“看起来”更好的是非常主观的,但function还是有很大的不同。 以下面的例子:

 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块内部,variables'j'增加到40,当j mod 4为0时打印出来,当j达到20时抛出exception。

在任何细节之前,这里的另一个例子:

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

 4 8 12 16 in catch block 24 28 32 36 40 

你有很大的不同:

而在try / catch突破循环

在保持循环活跃的同时尝试/赶上

如果它的全部或全部失败,那么第一种格式是有道理的。 如果你想能够处理/返回所有没有失败的元素,你需要使用第二种forms。 那些将是我在这些方法之间进行select的基本标准。 就个人而言,如果是全或无,我不会使用第二种forms。

只要你知道你需要在循环中完成什么,你可以把try catch放在循环之外。 但重要的是要明白,只要发生exception,循环就会结束,并不总是你想要的。 这实际上是基于Java的软件中的一个非常常见的错误。 人们需要处理一些项目,比如清空一个队列,并且错误地依靠一个处理所有可能exception的外部try / catch语句。 他们也可以只处理循环内的一个特定的exception,并不期望任何其他exception发生。 那么如果在循环内部没有处理exception,则循环将被“抢占”,可能会过早结束,而外部catch语句处理exception。

如果循环在生命中扮演着清空队列的angular色,那么在该队列真正清空之前,该循环很可能会结束。 很常见的错误。

在你的例子中没有function差异。 我发现你的第一个例子更具可读性。

你应该更喜欢内部版本的外部版本。 这只是规则的一个特定版本,移动循环之外的任何东西,你可以移动到循环之外。 根据IL编译器和JIT编译器,您的两个版本可能会或可能不会以不同的性能特点结束。

在另一个说明你应该看看float.TryParse或Convert.ToFloat。

如果你把try / catch放到循环中,你会在exception之后继续循环。 如果你把它放在循环之外,一旦抛出exception就会停下来。

为try / catch设置一个特殊的堆栈框架会增加额外的开销,但是JVM可能能够检测到你正在返回并优化这个事实。

取决于迭代的次数,性能差异可能可以忽略不计。

不过,我同意其他人的看法,让它在循环之外使循环体看起来更干净。

如果有机会,你会想要继续处理,而不是退出,如果有一个无效的数字,那么你会希望代码在循环内。

如果它在里面,那么你将获得try / catch结构的开销N次,而不是外部的一次。


每次调用Try / Catch结构时,都会增加方法执行的开销。 只需要处理这个结构的一点点内存和处理器就可以了。 如果你运行一个循环100次,而且为了假设,假设每个try / catch调用的代价是1 tick,那么在循环内部使用Try / Catch会花费100个tick,而如果它是1 tick循环之外。

例外的一个要点是鼓励第一种方式:让error handling合并和处理一次,而不是立即在每个可能的错误站点处理。

把它放进去 你可以继续处理(如果你想要的话),或者你可以抛出一个有用的exception,告诉客户端myString的值和包含坏值的数组的索引。 我认为NumberFormatException已经告诉你这个坏的值,但是原则是把所有有用的数据放在你抛出的exception中。 在程序的这个阶段,想一下在debugging器中你会感兴趣的东西。

考虑:

 try { // parse } catch (NumberFormatException nfe){ throw new RuntimeException("Could not parse as a Float: [" + myString + "] found at index: " + i, nfe); } 

在需要的时候,你会非常欣赏这样的例外,尽可能多的信息。

当我看看定位exception处理的一般问题时,我想添加我自己的0.02c关于两个竞争性的考虑:

  1. try-catch块(即在你的情况之外的循环之外)的“更广泛的”责任意味着,当稍后改变代码时,你可能会错误地添加一个由你现有的catch块处理的行; 可能无意中。 在你的情况下,这是不太可能的,因为你显式捕获一个NumberFormatException

  2. try-catch块的职责越“狭窄”,重构就越困难。 特别是当你(如你的情况)你正在执行一个“非本地”的指令从catch块( return null语句)。

这取决于故障处理。 如果你只是想跳过错误的元素,尝试在里面:

 for(int i = 0; i < max; i++) { String myString = ...; try { float myNum = Float.parseFloat(myString); myFloats[i] = myNum; } catch (NumberFormatException ex) { --i; } } 

在任何其他情况下,我宁愿在外面尝试。 代码更可读,更干净。 在错误情况下抛出IllegalArgumentException可能会更好,如果返回null。

我会把我的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. } 

因为如果你得到一个错误,你只想释放你的数据库连接(或select你喜欢的其他资源types)。

上面没有提到的另一个方面是,每个try-catch都会对堆栈产生一些影响,这会对recursion方法产生影响。

如果方法“outer()”调用方法“inner()”(可能会自动调用它),请尝试在方法“outer()”中定位try-catch。 在性能类中使用的一个简单的“堆栈崩溃”示例在try-catch处于内部方法时在大约6,400帧处失败,而在外部方法中大约在11,600处。

在现实世界中,如果您使用Composite模式并且具有大而复杂的嵌套结构,则这可能是个问题。