简体   繁体   English

应该尝试......赶上环路内外?

[英]Should try…catch go inside or outside a loop?

I have a loop that looks something like this: 我有一个看起来像这样的循环:

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

This is the main content of a method whose sole purpose is to return the array of floats. 这是方法的主要内容,其唯一目的是返回浮点数组。 I want this method to return null if there is an error, so I put the loop inside a try...catch block, like this: 我希望这个方法在出现错误时返回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;
}

But then I also thought of putting the try...catch block inside the loop, like this: 但后来我也考虑将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;
}

Is there any reason, performance or otherwise, to prefer one over the other? 是否有任何理由,表现或其他方式比较喜欢一个?


Edit: The consensus seems to be that it is cleaner to put the loop inside the try/catch, possibly inside its own method. 编辑:共识似乎是将循环置于try / catch中更清晰,可能在其自己的方法中。 However, there is still debate on which is faster. 但是,关于哪个更快,仍然存在争议。 Can someone test this and come back with a unified answer? 有人可以对此进行测试,并以统一的答案回来吗?

PERFORMANCE: 性能:

There is absolutely no performance difference in where the try/catch structures are placed. try / catch结构的放置位置绝对没有性能差异。 Internally, they are implemented as a code-range table in a structure that is created when the method is called. 在内部,它们被实现为在调用方法时创建的结构中的代码范围表。 While the method is executing, the try/catch structures are completely out of the picture unless a throw occurs, then the location of the error is compared against the table. 当该方法正在执行时,try / catch结构完全不在图片中,除非发生抛出,然后将错误的位置与表进行比较。

Here's a reference: http://www.javaworld.com/javaworld/jw-01-1997/jw-01-hood.html 以下是参考资料: http//www.javaworld.com/javaworld/jw-01-1997/jw-01-hood.html

The table is described about half-way down. 该表描述了一半。

Performance : as Jeffrey said in his reply, in Java it doesn't make much difference. 表现 :正如杰弗里在回答中所说,在Java中它没有太大的区别。

Generally , for readability of the code, your choice of where to catch the exception depends upon whether you want the loop to keep processing or not. 通常 ,为了代码的可读性,您选择捕获异常的位置取决于您是否希望循环继续处理。

In your example you returned upon catching an exception. 在您的示例中,您在捕获异常时返回。 In that case, I'd put the try/catch around the loop. 在那种情况下,我将try / catch放在循环中。 If you simply want to catch a bad value but carry on processing, put it inside. 如果你只是想抓住一个坏的值但继续处理,就把它放进去。

The third way : You could always write your own static ParseFloat method and have the exception handling dealt with in that method rather than your loop. 第三种方式 :您总是可以编写自己的静态ParseFloat方法,并在该方法中处理异常处理而不是循环。 Making the exception handling isolated to the loop itself! 使异常处理与循环本身隔离!

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

All right, after Jeffrey L Whitledge said that there was no performance difference (as of 1997), I went and tested it. 好吧,在Jeffrey L Whitledge说没有性能差异之后(截至1997年),我去测试了它。 I ran this small benchmark: 我跑了这个小基准:

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

I checked the resulting bytecode using javap to make sure that nothing got inlined. 我使用javap检查了生成的字节码,以确保没有任何内联。

The results showed that, assuming insignificant JIT optimizations, Jeffrey is correct ; 结果表明,假设JIT优化不明显, Jeffrey是正确的 ; there is absolutely no performance difference on Java 6, Sun client VM (I did not have access to other versions). 在Java 6,Sun客户端虚拟机上没有性能差异 (我没有访问其他版本)。 The total time difference is on the order of a few milliseconds over the entire test. 在整个测试中,总时间差大约为几毫秒。

Therefore, the only consideration is what looks cleanest. 因此,唯一的考虑因素是看起来最干净。 I find that the second way is ugly, so I will stick to either the first way or Ray Hayes's way . 我发现第二种方式是丑陋的,所以我会坚持第一种方式或Ray Hayes的方式

While performance might be the same and what "looks" better is very subjective, there is still a pretty big difference in functionality. 虽然性能可能相同,“看起来”更好是非常主观的,但功能上仍然存在很大差异。 Take the following example: 请看以下示例:

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

The while loop is inside the try catch block, the variable 'j' is incremented until it hits 40, printed out when j mod 4 is zero and an exception is thrown when j hits 20. while循环在try catch块内,变量'j'递增直到它达到40,当j mod 4为零时打印出来,当j到达20时抛出异常。

Before any details, here the other example: 在任何细节之前,这里是另一个例子:

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

Same logic as above, only difference is that the try/catch block is now inside the while loop. 与上面相同的逻辑,唯一的区别是try / catch块现在在while循环中。

Here comes the output (while in try/catch): 这是输出(在try / catch中):

4
8
12 
16
in catch block

And the other output (try/catch in while): 而另一个输出(try / catch in while):

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

There you have quite a significant difference: 你有很大的不同:

while in try/catch breaks out of the loop 而在try / catch中突破循环

try/catch in while keeps the loop active try / catch in while保持循环活动

I agree with all the performance and readability posts. 我同意所有的性能和可读性帖子。 However, there are cases where it really does matter. 但是,有些情况确实很重要。 A couple other people mentioned this, but it might be easier to see with examples. 其他几个人提到了这一点,但通过示例可能更容易看到。

Consider this slightly modified example: 考虑这个略微修改的例子:

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

If you want the parseAll() method to return null if there are any errors (like the original example), you'd put the try/catch on the outside like this: 如果你希望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;
}

In reality, you should probably return an error here instead of null, and generally I don't like having multiple returns, but you get the idea. 实际上,你应该在这里返回一个错误而不是null,通常我不喜欢多次返回,但你明白了。

On the other hand, if you want it to just ignore the problems, and parse whatever Strings it can, you'd put the try/catch on the inside of the loop like this: 另一方面,如果你希望它只是忽略这些问题,并解析它可以使用的任何字符串,你可以将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;
}

As already mentioned, the performance is the same. 如前所述,性能是一样的。 However, user experience isn't necessarily identical. 但是,用户体验不一定相同。 In the first case, you'll fail fast (ie after the first error), however if you put the try/catch block inside the loop, you can capture all the errors that would be created for a given call to the method. 在第一种情况下,您将快速失败(即在第一个错误之后),但是如果您将try / catch块放在循环中,则可以捕获为给定的方法调用创建的所有错误。 When parsing an array of values from strings where you expect some formatting errors, there are definitely cases where you'd like to be able to present all the errors to the user so that they don't need to try and fix them one by one. 在从期望出现格式错误的字符串解析值的数组时,肯定会有这样的情况:您希望能够将所有错误呈现给用户,这样他们就不需要逐个尝试修复它们。

As long as you are aware of what you need to accomplish in the loop you could put the try catch outside the loop. 只要你知道你需要在循环中完成什么,就可以将try catch放在循环之外。 But it is important to understand that the loop will then end as soon as the exception occurs and that may not always be what you want. 但重要的是要理解,一旦异常发生,循环就会结束,并且可能并不总是你想要的。 This is actually a very common error in Java based software. 这实际上是基于Java的软件中非常常见的错误。 People need to process a number of items, such as emptying a queue, and falsely rely on an outer try/catch statement handling all possible exceptions. 人们需要处理许多项目,例如清空队列,并错误地依赖处理所有可能异常的外部try / catch语句。 They could also be handling only a specific exception inside the loop and not expect any other exception to occur. 它们也可以只处理循环内的特定异常,而不期望发生任何其他异常。 Then if an exception occurs that is not handled inside the loop then the loop will be "preemted", it ends possibly prematurely and the outer catch statement handles the exception. 然后,如果发生了一个未在循环内部处理的异常,则循环将被“预先”,它可能过早地结束,外部catch语句处理异常。

If the loop had as its role in life to empty a queue then that loop very likely could end before that queue was really emptied. 如果循环在生活中扮演其角色来清空队列,那么该循环很可能在该队列真正被清空之前结束。 Very common fault. 非常常见的故障。

If its an all-or-nothing fail, then the first format makes sense. 如果它是全有或全无失败,那么第一种格式是有意义的。 If you want to be able to process/return all the non-failing elements, you need to use the second form. 如果您希望能够处理/返回所有非故障元素,则需要使用第二种形式。 Those would be my basic criteria for choosing between the methods. 那些将是我在方法之间进行选择的基本标准。 Personally, if it is all-or-nothing, I wouldn't use the second form. 就个人而言,如果是全有或全无,我就不会使用第二种形式。

In your examples there is no functional difference. 在您的示例中,没有功能差异。 I find your first example more readable. 我发现你的第一个例子更具可读性。

You should prefer the outer version over the inner version. 您应该更喜欢外部版本而不是内部版本。 This is just a specific version of the rule, move anything outside the loop that you can move outside the loop. 这只是规则的特定版本,移动循环外的任何东西,你可以移动到循环外。 Depending on the IL compiler and JIT compiler your two versions may or may not end up with different performance characteristics. 根据IL编译器和JIT编译器,您的两个版本可能会或可能不会以不同的性能特征结束。

On another note you should probably look at float.TryParse or Convert.ToFloat. 另外请注意,您应该查看float.TryParse或Convert.ToFloat。

If you put the try/catch inside the loop, you'll keep looping after an exception. 如果将try / catch放在循环中,则会在异常后继续循环。 If you put it outside the loop you'll stop as soon as an exception is thrown. 如果你把它放在循环之外,你会在抛出异常后立即停止。

My perspective would be try/catch blocks are necessary to insure proper exception handling, but creating such blocks has performance implications. 我的观点是try / catch块是确保正确的异常处理所必需的,但是创建这样的块会产生性能影响。 Since, Loops contain intensive repetitive computations, it is not recommended to put try/catch blocks inside loops. 由于,循环包含密集的重复计算,因此不建议将try / catch块放在循环中。 Additionally, it seems where this condition occurs, it is often "Exception" or "RuntimeException" which is caught. 此外,似乎在这种情况发生的地方,通常是“异常”或“RuntimeException”被捕获。 RuntimeException being caught in code should be avoided. 应避免在代码中捕获RuntimeException。 Again, if if you work in a big company it's essential to log that exception properly, or stop runtime exception to happen. 同样,如果您在大公司工作,则必须正确记录该异常,或者停止发生运行时异常。 Whole point of this description is PLEASE AVOID USING TRY-CATCH BLOCKS IN LOOPS 这个描述的全部内容是PLEASE AVOID USING TRY-CATCH BLOCKS IN LOOPS

If it's inside, then you'll gain the overhead of the try/catch structure N times, as opposed to just the once on the outside. 如果它在内部,那么你将获得尝试/捕获结构的开销N次,而不是只在外部的一次。


Every time a Try/Catch structure is called it adds overhead to the execution of the method. 每次调用Try / Catch结构时,都会增加方法执行的开销。 Just the little bit of memory & processor ticks needed to deal with the structure. 只需要处理结构所需的一点内存和处理器滴答。 If you're running a loop 100 times, and for hypothetical sake, let's say the cost is 1 tick per try/catch call, then having the Try/Catch inside the loop costs you 100 ticks, as opposed to only 1 tick if it's outside of the loop. 如果你正在运行一个循环100次,并且为了假设的缘故,假设每次尝试/捕获调用的成本是1个滴答,那么在循环内部使用Try / Catch会花费100个滴答,而不是只有1个滴答,如果它是循环之外。

setting up a special stack frame for the try/catch adds additional overhead, but the JVM may be able to detect the fact that you're returning and optimize this away. 为try / catch设置一个特殊的堆栈帧会增加额外的开销,但是JVM可能能够检测到你正在返回并优化它的事实。

depending on the number of iterations, performance difference will likely be negligible. 根据迭代次数,性能差异可能会微不足道。

However i agree with the others that having it outside the loop make the loop body look cleaner. 但是我同意其他人认为在循环之外使循环体看起来更干净。

If there's a chance that you'll ever want to continue on with the processing rather than exit if there an invalid number, then you would want the code to be inside the loop. 如果你有可能想要继续处理而不是退出,如果有一个无效的数字,那么你会希望代码在循环内。

异常的全部意义在于鼓励第一种风格:让错误处理得到整合和处理一次,而不是立即在每个可能的错误站点处理。

put it inside. 把它放进去 You can keep processing (if you want) or you can throw a helpful exception that tells the client the value of myString and the index of the array containing the bad value. 您可以继续处理(如果需要),也可以抛出一个有用的异常,告诉客户端myString的值和包含错误值的数组的索引。 I think NumberFormatException will already tell you the bad value but the principle is to place all the helpful data in the exceptions that you throw. 我认为NumberFormatException已经告诉你错误的值,但原则是将所有有用的数据放在你抛出的异常中。 Think about what would be interesting to you in the debugger at this point in the program. 想想在程序中此时调试器中你会感兴趣的内容。

Consider: 考虑:

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

In the time of need you will really appreciate an exception like this with as much information in it as possible. 在需要的时候,你会非常感谢这样的例外,并尽可能多地提供相关信息。

I's like to add my own 0.02c about two competing considerations when looking at the general problem of where to position exception handling: 在查看异常处理位置的一般问题时,我想添加自己的0.02c关于两个相互竞争的考虑因素:

  1. The "wider" the responsibility of the try-catch block (ie outside the loop in your case) means that when changing the code at some later point, you may mistakenly add a line which is handled by your existing catch block; try-catch块的“更广泛”的责任(即在你的情况下在循环之外)意味着在稍后更改代码时,你可能会错误地添加一条由现有catch块处理的行; possibly unintentionally. 可能是无意的。 In your case, this is less likely because you are explicitly catching a NumberFormatException 在您的情况下,这不太可能,因为您显式捕获NumberFormatException

  2. The "narrower" the responsibility of the try-catch block, the more difficult refactoring becomes. try-catch块的责任“越窄”,重构就越困难。 Particularly when (as in your case) you are executing a "non-local" instruction from within the catch block (the return null statement). 特别是当(在您的情况下)您正在catch块中执行“非本地”指令( return null语句)时。

That depends on the failure handling. 这取决于故障处理。 If you just want to skip the error elements, try inside: 如果您只想跳过错误元素,请尝试在内部:

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

In any other case i would prefer the try outside. 在任何其他情况下,我宁愿在外面尝试。 The code is more readable, it is more clean. 代码更易读,更干净。 Maybe it would be better to throw an IllegalArgumentException in the error case instead if returning null. 如果返回null,可能最好在错误情况下抛出IllegalArgumentException。

I'll put my $0.02 in. Sometimes you wind up needing to add a "finally" later on in your code (because who ever writes their code perfectly the first time?). 我会把我的0.02美元放进去。有时你最后需要在你的代码中添加一个“finally”(因为谁第一次完美地编写了他们的代码?)。 In those cases, suddenly it makes more sense to have the try/catch outside the loop. 在这些情况下,突然将try / catch放在循环之外更有意义。 For example: 例如:

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

Because if you get an error, or not, you only want to release your database connection (or pick your favorite type of other resource...) once. 因为如果您收到错误,您只想发布一次数据库连接(或选择您喜欢的其他类型的资源......)。

Another aspect not mentioned in the above is the fact that every try-catch has some impact on the stack, which can have implications for recursive methods. 上面没有提到的另一个方面是每个try-catch都会对堆栈产生一些影响,这可能会对递归方法产生影响。

If method "outer()" calls method "inner()" (which may call itself recursively), try to locate the try-catch in method "outer()" if possible. 如果方法“outer()”调用方法“inner()”(可以递归调用自身),尝试在方法“outer()”中找到try-catch,如果可能的话。 A simple "stack crash" example we use in a performance class fails at about 6,400 frames when the try-catch is in the inner method, and at about 11,600 when it is in the outer method. 我们在性能类中使用的一个简单的“堆栈崩溃”示例在try-catch在内部方法中时大约为6,400帧,而在外部方法中则为大约11,600帧。

In the real world, this can be an issue if you're using the Composite pattern and have large, complex nested structures. 在现实世界中,如果您使用Composite模式并且具有大而复杂的嵌套结构,则这可能是一个问题。

If you want to catch Exception for each iteration, or check at what iteration Exception is thrown and catch every Exceptions in an itertaion, place try...catch inside the loop. 如果你想为每次迭代捕获Exception,或者检查抛出异常的迭代并捕获迭代中的每个异常,请在循环中放置try ... catch。 This will not break the loop if Exception occurs and you can catch every Exception in each iteration throughout the loop. 如果发生异常,这将不会中断循环,并且您可以在整个循环中捕获每次迭代中的每个异常。

If you want to break the loop and examine the Exception whenever thrown, use try...catch out of the loop. 如果你想打破循环并在抛出时检查Exception,请使用try ... catch out of the loop。 This will break the loop and execute statements after catch (if any). 这将在catch(如果有)之后中断循环并执行语句。

It all depends on your need. 这一切都取决于你的需要。 I prefer using try...catch inside the loop while deploying as, if Exception occurs, the results aren't ambiguous and loop will not break and execute completely. 我更喜欢在部署时使用try ... catch循环,如果发生异常,结果不会模糊,循环不会中断并完全执行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM