[java] 캐치 루프 내부 또는 외부로 이동해야합니까?

다음과 같은 루프가 있습니다.

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 내부에 루프를 넣는 것이 더 깨끗한 것 같습니다. 그러나 여전히 어느 쪽이 더 빠른지에 대한 논쟁이 있습니다. 누군가 이것을 테스트하고 통일 된 답변으로 돌아올 수 있습니까?



답변

공연:

try / catch 구조의 위치에 성능 차이는 전혀 없습니다. 내부적으로는 메소드가 호출 될 때 작성되는 구조에서 코드 범위 테이블로 구현됩니다. 메소드가 실행되는 동안 throw / catch 구조는 throw가 발생하지 않는 한 완전히 그림에서 벗어난 것이므로 오류 위치는 테이블과 비교됩니다.

여기 참조가 있습니다 : http://www.javaworld.com/javaworld/jw-01-1997/jw-01-hood.html

표는 반쯤 아래로 설명되어 있습니다.


답변

성능 : 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;
    }
}


답변

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의 방법 중 하나를 고수 할 입니다.


답변

성능은 동일 할 수 있으며 “보이는”것이 더 주관적이지만 기능에는 여전히 큰 차이가 있습니다. 다음 예를 보자.

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가 0이면 인쇄되고 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

거기에는 상당한 차이가 있습니다.

try / catch가 루프를 벗어나는 동안

루프를 활성 상태로 유지하면서 try / catch in


답변

모든 성능 및 가독성 게시물에 동의합니다. 그러나 실제로 중요한 경우가 있습니다. 다른 사람들이 이것을 언급했지만 예제를 통해 더 쉽게 볼 수 있습니다.

이 약간 수정 된 예를 고려하십시오.

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


답변

이미 언급했듯이 성능은 동일합니다. 그러나 사용자 경험이 반드시 동일한 것은 아닙니다. 첫 번째 경우에는 빠르게 실패하지만 (즉, 첫 번째 오류 이후) try / catch 블록을 루프 안에 넣으면 주어진 메소드 호출에 대해 생성 된 모든 오류를 캡처 할 수 있습니다. 일부 형식화 오류가 예상되는 문자열 값 배열을 구문 분석 할 때 모든 오류를 사용자에게 제시하여 하나씩 수정하려고 할 필요가없는 경우가 있습니다. .


답변

그것의 전부 또는 아무것도 실패하면, 첫 번째 형식이 의미가 있습니다. 고장이 아닌 모든 요소를 ​​처리 / 반품하려면 두 번째 양식을 사용해야합니다. 그것들은 방법 중에서 선택하기위한 나의 기본 기준이 될 것입니다. 개인적으로, 아니면 아무것도 아니면, 나는 두 번째 양식을 사용하지 않을 것입니다.