代码之家  ›  专栏  ›  技术社区  ›  Michael Myers KitsuneYMG

应该试试。..抓进圈内还是圈外?

  •  211
  • Michael Myers KitsuneYMG  · 技术社区  · 16 年前

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

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

    但后来我也想到了把 尝试。..抓 循环中的block,如下所示:

    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中更为清晰,可能放在它自己的方法中。然而,关于哪个更快仍存在争议。有人能测试一下并得出统一的答案吗?

    21 回复  |  直到 12 年前
        1
  •  137
  •   Jeffrey L Whitledge    16 年前

    性能:

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

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

    这张桌子被描述在大约一半的地方。

        2
  •  78
  •   Community CDub    8 年前

    演出 :as 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;
        }
    }
    
        3
  •  48
  •   Community CDub    8 年前

    好吧,之后 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优化不显著, 杰弗里是对的 ;绝对有 在Java 6、Sun客户端VM上没有性能差异 (我无法访问其他版本)。在整个测试过程中,总时间差约为几毫秒。

    因此,唯一要考虑的是什么看起来最干净。我发现第二种方式很丑陋,所以我会坚持第一种方式或 Ray Hayes's way .

        4
  •  15
  •   seBaka28    9 年前

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

    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
    

    你有一个非常显著的区别:

    当try/catch中断循环时

    try/catch in while保持循环活动

        5
  •  14
  •   Matt N    13 年前

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

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

    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;
    }
    
        6
  •  5
  •   user19810    16 年前

    如前所述,性能是相同的。然而,用户体验并不一定完全相同。在第一种情况下,你会很快失败(即在第一个错误之后),但是如果你把try/catch块放在循环中,你可以捕获对该方法的给定调用所创建的所有错误。在解析字符串中的值数组时,如果你预期会出现一些格式错误,那么在某些情况下,你肯定希望能够向用户呈现所有错误,这样他们就不需要逐一尝试修复它们。

        7
  •  4
  •   Joe Skora    16 年前

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

        8
  •  4
  •   Gunnar Forsgren - Mobimation    13 年前

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

    如果循环的作用是清空队列,那么循环很可能会在队列真正清空之前结束。非常常见的故障。

        9
  •  2
  •   Jamie    16 年前

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

        10
  •  2
  •   Orion Adrian    16 年前

    在您的示例中,没有功能差异。我觉得你的第一个例子更易读。

        11
  •  2
  •   Joel Coehoorn    16 年前

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

    另一方面,你可能应该看看浮点数。尝试解析或转换。ToFloat。

        12
  •  2
  •   surendrapanday 9swampy    6 年前

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

        13
  •  1
  •   Matt    16 年前

    我想加上我自己的 0.02c 在考虑异常处理的位置这一一般问题时,有两个相互竞争的考虑因素:

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

    2. “狭义”的责任 异常处理机制 块,重构就越困难。特别是当(如您的情况)您从内部执行“非本地”指令时 赶上 块(the return null 声明)。

        14
  •  1
  •   Stephen Wrighton    16 年前

    如果你想在每次迭代中捕获Exception,或者检查在什么迭代中抛出Exception并捕获迭代中的每个Exception,请尝试一下。..抓住循环内部。如果发生Exception,这不会破坏循环,并且您可以在整个循环的每次迭代中捕获每个Exception。

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

    这一切都取决于你的需要。我更喜欢用try。..在部署时在循环内捕获,如果发生异常,结果不会含糊不清,循环也不会完全中断和执行。

        15
  •  1
  •   wnoise    16 年前

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

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

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

    如果你有可能希望继续处理,而不是在出现无效数字时退出,那么你会希望代码在循环中。

        16
  •  1
  •   Kyle Dyer    16 年前

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


    每次调用Try/Catch结构时,都会给方法的执行增加开销。只有一点点记忆和;处理器需要处理该结构。如果你运行一个循环100次,并且为了假设,假设每次try/catch调用的成本为1分,那么在循环内运行try/catch需要花费100分,而在循环外只需要1分。

        17
  •  1
  •   oxbow_lakes    16 年前

    异常的全部要点是鼓励第一种风格:让错误处理被整合和处理一次,而不是在每个可能的错误位置立即处理。

        18
  •  1
  •   Arne Burmeister    16 年前

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

    考虑:

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

    在需要的时候,你会非常感激这样一个例外,尽可能多地提供信息。

        19
  •  1
  •   Ogre Psalm33    16 年前

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

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

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

        20
  •  1
  •   Jeff Hill    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.
    }
    

    因为无论是否出现错误,您只想释放一次数据库连接(或选择您最喜欢的其他资源类型…)。

        21
  •  1
  •   Junaid Pathan    6 年前

    上面没有提到的另一个方面是,每次尝试捕获都有 一些 对堆栈的影响,这可能会对递归方法产生影响。

    如果方法“outer()”调用方法“inner()”(可以递归调用自身),如果可能的话,尝试在方法“outer()”中定位try-catch。当try-catch在内部方法中时,我们在性能类中使用的一个简单的“堆栈崩溃”示例在大约6400帧时失败,当它在外部方法中时在大约11600帧时失败。

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