197

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

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 中更干净,可能放在它自己的方法中。但是,仍然存在关于哪个更快的争论。有人可以对此进行测试并给出统一的答案吗?

4

21 回答 21

138

表现:

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

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

这张桌子被描述到大约一半。

于 2008-09-26T20:09:17.670 回答
78

性能:正如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;
    }
}
于 2008-09-26T19:57:12.067 回答
48

好吧,在Jeffrey L Whitledge 说没有性能差异(截至 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 的方式

于 2008-09-29T16:55:35.973 回答
15

虽然性能可能相同,并且“看起来”更好是非常主观的,但功能上仍然存在很大差异。举个例子:

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

和另一个输出(尝试/捕获同时):

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

那里有很大的不同:

while in try/catch 跳出循环

try/catch in while 保持循环处于活动状态

于 2015-10-30T12:27:33.797 回答
14

我同意所有性能和可读性的帖子。但是,在某些情况下它确实很重要。其他几个人提到了这一点,但通过示例可能更容易看到。

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

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;
}
于 2008-09-30T20:42:21.350 回答
5

如前所述,性能是相同的。但是,用户体验不一定相同。在第一种情况下,您将很快失败(即在第一个错误之后),但是如果您将 try/catch 块放入循环中,您可以捕获为给定的方法调用创建的所有错误。从字符串中解析值数组时,您希望出现一些格式错误,在某些情况下,您肯定希望能够将所有错误呈现给用户,这样他们就不需要一个一个地尝试修复它们.

于 2009-02-13T01:22:16.777 回答
4

如果它是全有或全无的失败,那么第一种格式是有意义的。如果您希望能够处理/返回所有非失败元素,则需要使用第二种形式。这些将是我在这些方法之间进行选择的基本标准。就个人而言,如果它是全有或全无,我不会使用第二种形式。

于 2008-09-26T19:59:38.333 回答
4

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

如果循环在生活中的角色是清空队列,那么该循环很可能会在该队列真正清空之前结束。很常见的故障。

于 2012-08-31T09:08:33.973 回答
3

我的观点是 try/catch 块对于确保正确的异常处理是必要的,但是创建这样的块会影响性能。由于循环包含密集的重复计算,因此不建议将 try/catch 块放在循环内。此外,似乎发生这种情况的地方,通常是“异常”或“运行时异常”被捕获。应避免在代码中捕获 RuntimeException。同样,如果您在一家大公司工作,则必须正确记录该异常,或停止运行时异常的发生。这个描述的重点是PLEASE AVOID USING TRY-CATCH BLOCKS IN LOOPS

于 2019-02-13T21:56:01.320 回答
2

在您的示例中,没有功能差异。我发现您的第一个示例更具可读性。

于 2008-09-26T19:55:34.397 回答
2

您应该更喜欢外部版本而不是内部版本。这只是规则的特定版本,将任何可以移出循环的内容移出循环。根据 IL 编译器和 JIT 编译器,您的两个版本可能会或可能不会以不同的性能特征结束。

另一方面,您可能应该查看 float.TryParse 或 Convert.ToFloat。

于 2008-09-26T19:57:18.073 回答
2

如果您将 try/catch 放入循环中,您将在异常后继续循环。如果你把它放在循环之外,一旦抛出异常,你就会停止。

于 2008-09-26T19:58:23.937 回答
2

0.02c在查看异常处理位置的一般问题时,我想添加我自己的两个相互竞争的考虑:

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

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

于 2008-10-05T15:41:53.697 回答
2

如果您想为每次迭代捕获异常,或者检查在哪个迭代抛出异常并捕获迭代中的每个异常,请将 try...catch 放在循环内。如果发生异常,这不会中断循环,并且您可以在整个循环中的每次迭代中捕获每个异常。

如果您想打破循环并在抛出异常时检查异常,请使用 try...catch 退出循环。这将中断循环并在 catch 之后执行语句(如果有的话)。

这一切都取决于您的需要。我更喜欢在部署时在循环内使用 try...catch,因为如果发生异常,结果不会模棱两可,循环不会中断并完全执行。

于 2018-12-23T14:59:58.193 回答
1

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


每次调用 Try/Catch 结构时,都会增加方法执行的开销。只需一点点内存和处理器的滴答声就可以处理该结构。如果您正在运行一个循环 100 次,并且出于假设的原因,假设成本是每次 try/catch 调用的 1 个滴答声,那么在循环中使用 Try/Catch 会花费您 100 个滴答声,而不是只有 1 个滴答声在循环之外。

于 2008-09-26T19:57:38.417 回答
1

为 try/catch 设置一个特殊的堆栈帧会增加额外的开销,但 JVM 可能能够检测到您正在返回的事实并对其进行优化。

根据迭代次数,性能差异可能可以忽略不计。

然而,我同意其他人的观点,将它放在循环之外会使循环体看起来更干净。

如果您有可能想要继续处理而不是在存在无效数字时退出,那么您会希望代码位于循环内。

于 2008-09-26T20:01:47.097 回答
1

例外的全部意义在于鼓励第一种风格:让错误处理合并并处理一次,而不是立即在每个可能的错误位置处理。

于 2008-09-26T23:11:28.013 回答
1

把它放在里面。您可以继续处理(如果需要),或者您可以抛出一个有用的异常,告诉客户端 myString 的值和包含错误值的数组的索引。我认为 NumberFormatException 已经告诉你错误的值,但原则是将所有有用的数据放在你抛出的异常中。想想在程序的这一点上调试器中你会感兴趣的是什么。

考虑:

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

在需要的时候,您会非常欣赏这样的异常,其中包含尽可能多的信息。

于 2008-10-01T05:23:00.147 回答
1

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

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

在任何其他情况下,我更喜欢外面的尝试。代码更易读,更干净。如果返回 null,也许在错误情况下抛出 IllegalArgumentException 会更好。

于 2008-10-07T08:13:14.897 回答
1

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

因为如果你得到一个错误,或者没有,你只想释放你的数据库连接(或选择你最喜欢的其他资源类型......)一次。

于 2008-10-09T16:30:41.803 回答
1

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

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

在现实世界中,如果您使用的是复合模式并且具有大型、复杂的嵌套结构,这可能会成为一个问题。

于 2010-03-11T19:06:16.940 回答