我有一个这样的循环:

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

这是一个方法的主要内容,该方法的唯一目的是返回浮点数数组。我想让这个方法在出现错误时返回null,所以我把循环放在try…Catch block,像这样:

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

但后来我也想到试一试……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中更干净,可能是在它自己的方法中。然而,关于哪个速度更快仍存在争议。有人能测试一下并给出一个统一的答案吗?


当前回答

性能:

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

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

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

其他回答

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

好吧,在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上绝对没有性能差异(我没有访问其他版本)。整个测试的总时间差在几毫秒的数量级上。

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

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

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

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

性能:就像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;
    }
}

性能:

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

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

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