206 Stimmen

Sollte versuchen...fangen innerhalb oder außerhalb einer Schleife zu gehen?

Ich habe eine Schleife, die in etwa so aussieht:

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

Dies ist der Hauptinhalt einer Methode, deren einziger Zweck es ist, das Array von Floats zurückzugeben. Ich möchte, dass diese Methode zurückgibt null wenn ein Fehler auftritt, also habe ich die Schleife in eine try...catch Block, etwa so:

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

Aber dann habe ich auch daran gedacht, die try...catch Block innerhalb der Schleife, etwa so:

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

Gibt es irgendeinen Grund, der dafür spricht, das eine dem anderen vorzuziehen, sei es aus Leistungsgründen oder aus anderen Gründen?


Edit : Der Konsens scheint zu sein, dass es sauberer ist, die Schleife innerhalb der try/catch zu platzieren, möglicherweise innerhalb einer eigenen Methode. Es gibt jedoch immer noch eine Debatte darüber, was schneller ist. Kann jemand dies testen und mit einer einheitlichen Antwort zurückkommen?

2 Stimmen

Ich weiß nicht, wie es um die Leistung bestellt ist, aber der Code sieht mit dem Try-Catch außerhalb der for-Schleife sauberer aus.

141voto

Jeffrey L Whitledge Punkte 55524

PERFORMANCE:

Es gibt absolut keinen Leistungsunterschied in Bezug auf die Platzierung der try/catch-Strukturen. Intern sind sie als Code-Range-Tabelle in einer Struktur implementiert, die beim Aufruf der Methode erstellt wird. Während die Methode ausgeführt wird, sind die Try/Catch-Strukturen völlig unauffällig, es sei denn, es kommt zu einem Throw, dann wird der Fehlerort mit der Tabelle verglichen.

Hier ist eine Referenz: http://www.javaworld.com/javaworld/jw-01-1997/jw-01-hood.html

Die Tabelle ist etwa auf halber Strecke beschrieben.

0 Stimmen

Okay, Sie sagen also, dass es außer der Ästhetik keinen Unterschied gibt. Können Sie eine Quelle angeben?

2 Stimmen

Danke. Ich nehme an, dass sich die Dinge seit 1997 geändert haben könnten, aber sie würden es kaum schaffen. weniger effizient.

1 Stimmen

Absolut ist ein schwieriges Wort. In einigen Fällen kann es Compiler-Optimierungen behindern. Das sind keine direkten Kosten, aber es kann ein indirekter Leistungsnachteil sein.

80voto

Ray Hayes Punkte 14625

Leistung : als Jeffrey sagte er in seiner Antwort, in Java mache das keinen großen Unterschied.

Im Allgemeinen Um die Lesbarkeit des Codes zu gewährleisten, hängt die Entscheidung, wo die Ausnahme abgefangen werden soll, davon ab, ob die Schleife weiterlaufen soll oder nicht.

In Ihrem Beispiel sind Sie nach dem Auffangen einer Ausnahme zurückgekehrt. In diesem Fall würde ich die try/catch-Schleife um die Schleife legen. Wenn Sie einfach einen schlechten Wert abfangen, aber die Verarbeitung fortsetzen wollen, setzen Sie es innerhalb.

Der dritte Weg : Sie können jederzeit eine eigene statische ParseFloat-Methode schreiben und die Ausnahmebehandlung in dieser Methode und nicht in Ihrer Schleife durchführen. Dadurch wird die Behandlung von Ausnahmen von der Schleife selbst isoliert!

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

1 Stimmen

Sehen Sie genauer hin. In beiden Fällen verlässt er die erste Ausnahme. Das habe ich zuerst auch gedacht.

2 Stimmen

Ich war mir dessen bewusst, deshalb habe ich gesagt, dass ich in seinem Fall, da er zurückkehrt, wenn er auf ein Problem stößt, eine externe Erfassung verwenden würde. Zur Klarstellung: Das ist die Regel, die ich anwenden würde...

0 Stimmen

Netter Code, aber leider hat Java nicht den Luxus von Out-Parametern.

49voto

Michael Myers Punkte 183216

In Ordnung, nach Jeffrey L. Whitledge sagte dass es keinen Leistungsunterschied gibt (Stand 1997), habe ich es getestet. Ich habe diesen kleinen Benchmark durchgeführt:

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

Ich habe den resultierenden Bytecode mit javap überprüft, um sicherzustellen, dass nichts inlined wurde.

Die Ergebnisse zeigen, dass unter der Annahme unbedeutender JIT-Optimierungen, Jeffrey hat Recht Es gibt absolut kein Leistungsunterschied bei Java 6, Sun Client VM (Ich hatte keinen Zugang zu anderen Versionen). Der gesamte Zeitunterschied liegt in der Größenordnung von ein paar Millisekunden über den gesamten Test.

Es kommt also nur darauf an, was am saubersten aussieht. Ich finde, dass die zweite Möglichkeit hässlich ist, also bleibe ich entweder bei der ersten Möglichkeit oder Ray Hayes' Weg .

0 Stimmen

Wenn der Exception-Handler außerhalb der Schleife fließt, dann denke ich, es ist am klarsten, es zu platzieren außerhalb die Schleife -- Anstatt eine catch-Klausel scheinbar innerhalb der Schleife zu platzieren, die dennoch zurückkehrt. Ich verwende eine Zeile Leerzeichen um den "gefangenen" Körper solcher "catch-all"-Methoden, um die Körper von der Totalversuchsblock .

17voto

seBaka28 Punkte 842

Auch wenn die Leistung gleich sein mag und es sehr subjektiv ist, was besser "aussieht", gibt es doch einen ziemlich großen Unterschied in der Funktionalität. Nehmen Sie das folgende Beispiel:

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

Die while-Schleife befindet sich innerhalb des try-catch-Blocks, die Variable 'j' wird inkrementiert, bis sie 40 erreicht, ausgedruckt, wenn j mod 4 Null ist, und eine Ausnahme wird ausgelöst, wenn j 20 erreicht.

Bevor wir ins Detail gehen, hier das andere Beispiel:

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

Dieselbe Logik wie oben, der einzige Unterschied ist, dass der try/catch-Block jetzt innerhalb der while-Schleife liegt.

Hier kommt die Ausgabe (während in try/catch):

4
8
12 
16
in catch block

Und die andere Ausgabe (try/catch in while):

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

Hier besteht ein erheblicher Unterschied:

while in try/catch bricht aus der Schleife aus

try/catch in while hält die Schleife aktiv

1 Stimmen

Also setzen try{} catch() {} um die Schleife herum, wenn die Schleife als eine einzige "Einheit" behandelt wird und die Entdeckung eines Problems in dieser Einheit die gesamte "Einheit" (oder in diesem Fall die Schleife) ins Wanken bringt. Setzen Sie try{} catch() {} innerhalb der Schleife, wenn Sie eine einzelne Iteration der Schleife oder ein einzelnes Element in einem Array als eine ganze "Einheit" behandeln. Wenn in dieser "Einheit" eine Ausnahme auftritt, wird dieses Element einfach übersprungen und mit der nächsten Iteration der Schleife fortgefahren.

14voto

Matt N Punkte 1078

Ich stimme allen Beiträgen über Leistung und Lesbarkeit zu. Es gibt jedoch Fälle, in denen es wirklich wichtig ist. Ein paar andere Leute erwähnten dies, aber es könnte einfacher sein, mit Beispielen zu sehen.

Betrachten Sie dieses leicht abgewandelte Beispiel:

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

Wenn Sie möchten, dass die parseAll()-Methode null zurückgibt, wenn es irgendwelche Fehler gibt (wie das ursprüngliche Beispiel), würden Sie die try/catch auf der Außenseite wie folgt setzen:

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 Wirklichkeit sollten Sie hier wahrscheinlich einen Fehler anstelle von null zurückgeben, und im Allgemeinen mag ich es nicht, mehrere Rückgaben zu haben, aber Sie verstehen die Idee.

Auf der anderen Seite, wenn Sie wollen, dass es nur die Probleme zu ignorieren, und parsen, was Strings es kann, würden Sie die Try/Catch auf der Innenseite der Schleife wie diese setzen:

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

2 Stimmen

Deshalb habe ich gesagt: "Wenn Sie einfach nur einen schlechten Wert auffangen, aber weiterverarbeiten wollen, stecken Sie ihn hinein."

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X