Spróbuj ... złapać go wewnątrz czy na zewnątrz pętli?

Mam pętlę, która wygląda mniej więcej tak:

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

Jest to główna Zawartość metody, której jedynym celem jest zwrócenie tablicy pływaków. Chcę, aby ta metoda zwróciła null, jeśli wystąpi błąd, więc umieszczam pętlę wewnątrz try...catch bloku, Tak:

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

Ale wtedy też pomyślałem o umieszczeniu try...catch bloku wewnątrz pętli, Tak:

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

Czy Jest jakiś powód, wydajność lub w inny sposób, aby wolą jeden niż inne?


Edit: konsensus wydaje się być taki, że czystsze jest umieszczenie pętli wewnątrz try/catch, ewentualnie wewnątrz własnej metody. Jednak nadal trwa debata, która jest szybsza. Czy ktoś może to przetestować i wrócić z ujednoliconą odpowiedzią?

Author: Michael Myers, 2008-09-26

21 answers

Wydajność:

Nie ma absolutnie żadnej różnicy w wydajności w miejscu, w którym znajdują się struktury try/catch. Wewnętrznie są one zaimplementowane jako tabela zakresu kodu w strukturze, która jest tworzona podczas wywoływania metody. Podczas wykonywania metody, struktury try/catch są całkowicie poza obrazem, chyba że wystąpi rzut, a następnie lokalizacja błędu jest porównywana z tabelą.

Oto odniesienie: http://www.javaworld.com/javaworld/jw-01-1997/jw-01-hood.html

Tabela jest opisana w połowie drogi w dół.

 133
Author: Jeffrey L Whitledge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-26 20:17:43

Wydajność : Jak Jeffrey powiedział w swojej odpowiedzi, w Javie to nie robi wielkiej różnicy.

Ogólnie , dla czytelności kodu, wybór miejsca przechwycenia wyjątku zależy od tego, czy chcesz, aby pętla nadal przetwarzała, czy nie.

W twoim przykładzie powróciłeś po złapaniu wyjątku. W takim razie, chciałbym umieścić spróbować / złapać wokół pętli. Jeśli po prostu chcesz złapać złą wartość, ale kontynuuj przetwarzanie, włóż ją do środka.

Trzeci sposób: zawsze możesz napisać własną statyczną metodę ParseFloat i mieć do czynienia z obsługą WYJĄTKÓW w tej metodzie, a nie pętli. Czyniąc obsługę wyjątków odizolowanymi do samej pętli!

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;
    }
}
 72
Author: Ray Hayes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:46:24

W porządku, po tym jak Jeffrey L Whitledge powiedział, że nie ma różnicy w wydajności (od 1997 roku), poszedłem ją przetestować. Sprawdziłem ten mały benchmark:

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

Sprawdziłem uzyskany bajt za pomocą javap, aby upewnić się, że nic nie zostało zainlinowane.

Wyniki pokazały, że zakładając nieznaczne optymalizacje JIT, Jeffrey jest poprawny ; nie ma absolutnie żadnej różnicy w wydajności na Java 6, Sun client VM (nie miałem dostępu do innych wersji). Całkowita różnica czasu jest rzędu kilku milisekund w całym teście.

Dlatego jedynym rozważaniem jest to, co wygląda najczystiej. Uważam, że druga droga jest brzydka, więc będę trzymać się pierwszej drogi lub drogi Raya Hayesa .

 47
Author: Michael Myers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:22

Chociaż wydajność może być taka sama, a to, co" wygląda " lepiej, jest bardzo subiektywne, nadal istnieje dość duża różnica w funkcjonalności. Weźmy następujący przykład:

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

Pętla while znajduje się wewnątrz bloku try catch, zmienna 'j' jest zwiększana do 40, wypisywana, gdy J mod 4 wynosi zero, a wyjątek jest wyrzucany, gdy J trafi 20.

Przed jakimikolwiek szczegółami, tutaj drugi przykład:

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

Ta sama logika jak powyżej, jedyna różnica polega na tym, że try/catch block jest teraz wewnątrz pętli while.

Oto wyjście (podczas try/catch):

4
8
12 
16
in catch block

I inne wyjście (try/catch in while):

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

Tam masz dość znaczącą różnicę:

Gdy w try / catch wyłamuje się z pętli

Try / catch in while keeps the loop active

 15
Author: seBaka28,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-30 12:27:33

Zgadzam się ze wszystkimi postami o wydajności i czytelności. Są jednak przypadki, w których to naprawdę ma znaczenie. Kilka innych osób wspomniało o tym, ale łatwiej byłoby to zobaczyć na przykładach.

Rozważmy ten nieco zmodyfikowany przykład:

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

Jeśli chcesz, aby metoda parseAll() zwracała null, jeśli są jakieś błędy( jak w oryginalnym przykładzie), powinieneś umieścić try / catch Na zewnątrz w następujący sposób:

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

W rzeczywistości, prawdopodobnie powinieneś zwrócić błąd tutaj zamiast null, i ogólnie nie lubię mieć wielu zwrotów, ale masz pomysł.

Z drugiej strony, jeśli chcesz, aby po prostu zignorowała problemy i przeanalizowała wszystkie możliwe ciągi, umieścisz try / catch na wewnętrznej stronie pętli w następujący sposób:

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;
}
 14
Author: Matt N,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-24 19:59:04

Jak już wspomniano, wydajność jest taka sama. Jednak doświadczenie użytkownika niekoniecznie jest identyczne. W pierwszym przypadku szybko się nie uda( tj. po pierwszym błędzie), jednak jeśli umieścisz blok try/catch wewnątrz pętli, możesz przechwycić wszystkie błędy, które zostaną utworzone dla danego wywołania metody. Podczas parsowania tablicy wartości z łańcuchów, w których spodziewasz się błędów formatowania, są zdecydowanie przypadki, w których chciałbyś być w stanie przedstawić wszystkie błędy użytkownika, aby nie musieli próbować naprawiać ich jeden po drugim.

 5
Author: user19810,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-13 01:22:16

Jeśli to wszystko albo nic, to pierwszy format ma sens. Jeśli chcesz być w stanie przetworzyć/zwrócić wszystkie niedziałające elementy, musisz użyć drugiego formularza. To byłyby moje podstawowe kryteria wyboru pomiędzy metodami. Osobiście, jeśli to wszystko albo nic, nie używałbym drugiej formy.

 4
Author: Joe Skora,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-26 19:59:38

Dopóki jesteś świadomy tego, co musisz osiągnąć w pętli, możesz umieścić try catch poza pętlą. Ale ważne jest, aby zrozumieć, że pętla zakończy się, gdy tylko wystąpi wyjątek i to nie zawsze może być to, czego chcesz. Jest to w rzeczywistości bardzo częsty błąd w oprogramowaniu opartym na Javie. Ludzie muszą przetwarzać wiele elementów, takich jak opróżnianie kolejki, i fałszywie polegać na zewnętrznej instrukcji try/catch obsługującej wszystkie możliwe wyjątki. Mogą też obsługiwać tylko określony wyjątek wewnątrz pętli i nie należy oczekiwać wystąpienia żadnego innego wyjątku. Następnie, jeśli wystąpi wyjątek, który nie jest obsługiwany wewnątrz pętli, pętla zostanie "preemted", kończy się prawdopodobnie przedwcześnie i instrukcja outer catch obsługuje wyjątek.

Jeśli pętla miała za swoją rolę w życiu opróżnić kolejkę, to pętla prawdopodobnie mogłaby się skończyć, zanim kolejka ta została naprawdę opróżniona. Bardzo częsty błąd.

 4
Author: Gunnar Forsgren - Mobimation,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-31 09:08:33

W Twoich przykładach nie ma różnicy funkcjonalnej. Twój pierwszy przykład jest bardziej czytelny.

 2
Author: Jamie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-26 19:55:34

Powinieneś preferować wersję zewnętrzną niż wewnętrzną. Jest to tylko konkretna wersja reguły, przenieś wszystko poza pętlę, które możesz przenieść poza pętlę. W zależności od kompilatora IL i kompilatora JIT dwie wersje mogą, ale nie muszą, mieć różne charakterystyki wydajności.

W innej notce powinieneś chyba spojrzeć na float.Wypróbuj lub przekonwertuj.ToFloat.

 2
Author: Orion Adrian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-26 19:57:18

Jeśli umieścisz try / catch wewnątrz pętli, będziesz nadal zapętlać po wyjątku. Jeśli umieścisz go poza pętlą, zatrzymasz się zaraz po wyrzuceniu wyjątku.

 2
Author: Joel Coehoorn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-26 19:58:23

Moim zdaniem bloki try/catch są niezbędne do zapewnienia właściwej obsługi wyjątków, ale tworzenie takich bloków ma wpływ na wydajność. Ponieważ pętle zawierają intensywne powtarzalne obliczenia, nie zaleca się umieszczania bloków try/catch wewnątrz pętli. Ponadto wydaje się, że tam, gdzie występuje ten warunek, często jest to" wyjątek "lub" RuntimeException", który jest przechwytywany. Należy unikać przechwytywania RuntimeException w kodzie. Ponownie, jeśli pracujesz w dużej firmie, konieczne jest, aby zapisz ten wyjątek poprawnie lub zatrzymaj wyjątek runtime, aby się wydarzył. Cały punkt tego opisu to PLEASE AVOID USING TRY-CATCH BLOCKS IN LOOPS

 2
Author: surendrapanday,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-02-13 23:23:44

Ustawienie specjalnej ramki stosu dla try / catch dodaje dodatkowe koszty, ale JVM może być w stanie wykryć fakt powrotu i zoptymalizować to.

W zależności od liczby iteracji, różnica w wydajności będzie prawdopodobnie znikoma.

Jednak zgadzam się z innymi, że posiadanie go poza pętlą sprawia, że ciało pętli wygląda czystiej.

Jeśli istnieje szansa, że będziesz chciał kontynuować przetwarzanie, a nie zakończyć, jeśli istnieje nieprawidłowy numer, wtedy chcesz, aby Kod był wewnątrz pętli.

 1
Author: Matt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-26 20:01:47

Jeśli jest w środku, zyskasz narzut struktury try/catch N razy, w przeciwieństwie do tylko raz na zewnątrz.


Za każdym razem, gdy wywołana jest struktura Try/Catch, dodaje ona narzut do wykonania metody. Wystarczy odrobina pamięci i procesora, aby poradzić sobie ze strukturą. Jeśli uruchamiasz pętlę 100 razy, a dla hipotetycznego dobra, powiedzmy, że koszt to 1 tick za połączenie try/catch, to posiadanie Try / Catch wewnątrz pętli kosztuje 100 kleszcze, w przeciwieństwie do tylko 1 kleszcza, jeśli jest poza pętlą.

 1
Author: Stephen Wrighton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-26 20:19:58

Cały punkt WYJĄTKÓW ma na celu zachęcenie do pierwszego stylu: umożliwienie skonsolidowania i obsługi błędów raz, a nie natychmiast na każdym możliwym miejscu błędu.

 1
Author: wnoise,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-26 23:11:28

Włóż to do środka. Możesz kontynuować przetwarzanie (jeśli chcesz) lub możesz rzucić pomocny wyjątek, który informuje Klienta o wartości myString i indeksie tablicy zawierającej złą wartość. Myślę, że NumberFormatException już powie ci złą wartość, ale zasadą jest umieszczenie wszystkich przydatnych danych w wyjątkach, które rzucasz. Zastanów się, co byłoby dla Ciebie interesujące w debuggerze w tym momencie programu.

Rozważmy:

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

W czasie potrzebujesz naprawdę docenią wyjątek jak ten z jak najwięcej informacji w nim, jak to możliwe.

 1
Author: Kyle Dyer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-01 05:23:00

Chciałbym dodać własne 0.02c o dwóch konkurencyjnych rozważaniach, patrząc na ogólny problem, gdzie umieścić obsługę wyjątków:

  1. "szersza" odpowiedzialność try-catch bloku (tj. poza pętlą w Twoim przypadku) oznacza, że podczas zmiany kodu w późniejszym czasie możesz omyłkowo dodać linię, którą obsługuje twój istniejący catch blok; być może nieumyślnie. W Twoim przypadku jest to mniej prawdopodobne, ponieważ wyraźnie łapiesz NumberFormatException

  2. Im "węższa" odpowiedzialność bloku try-catch, tym trudniejsza staje się refaktoryzacja. Szczególnie, gdy (jak w Twoim przypadku) wykonujesz" nielokalną " instrukcję z bloku catch (Instrukcja return null).

 1
Author: oxbow_lakes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-05 15:41:53

To zależy od obsługi awarii. Jeśli chcesz tylko pominąć elementy błędu, spróbuj wewnątrz:

for(int i = 0; i < max; i++) {
    String myString = ...;
    try {
        float myNum = Float.parseFloat(myString);
        myFloats[i] = myNum;
    } catch (NumberFormatException ex) {
        --i;
    }
}
W każdym innym przypadku wolałbym spróbować Na Zewnątrz. Kod jest bardziej czytelny, jest bardziej czysty. Być może lepiej byłoby rzucić IllegalArgumentException w przypadku błędu zamiast zwracać null.
 1
Author: Arne Burmeister,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-07 08:13:14

Włożę swoje 0,02 dolara. Czasami trzeba dodać" w końcu " później w kodzie (bo kto pisze swój kod idealnie za pierwszym razem?). W takich przypadkach nagle bardziej sensowne jest mieć try / catch poza pętlą. Na przykład:

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

Ponieważ Jeśli pojawi się błąd, lub nie, chcesz tylko zwolnić połączenie z bazą danych (lub wybrać swój ulubiony typ innego zasobu...) raz.

 1
Author: Ogre Psalm33,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-09 16:30:41

Innym aspektem nie wspomnianym powyżej jest fakt, że każdy try-catch ma jakiś Wpływ na stos, co może mieć implikacje dla metod rekurencyjnych.

Jeśli metoda" outer () "wywołuje metodę" inner () "(która może wywoływać się rekurencyjnie), spróbuj zlokalizować try-catch w metodzie" outer ()", jeśli to możliwe. Prosty przykład "stack crash", którego używamy w klasie wydajności zawodzi przy około 6400 klatkach, gdy try-catch jest w metodzie wewnętrznej, i przy około 11600, gdy jest w metoda zewnętrzna.

W świecie rzeczywistym może to być problem, jeśli używasz wzorca złożonego i masz duże, złożone struktury zagnieżdżone.

 1
Author: Jeff Hill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-11 19:06:16

Jeśli chcesz wyłapać wyjątki dla każdej iteracji lub sprawdzić, w jakim wyjątku iteracji jest wyrzucany i wyłapać wszystkie wyjątki w iteracji, umieść try...Złap w pętli. Nie spowoduje to przerwania pętli, jeśli wystąpi wyjątek i możesz przechwycić każdy wyjątek w każdej iteracji w całej pętli.

Jeśli chcesz przerwać pętlę i sprawdzić wyjątek po wyrzuceniu, użyj try...Złap z pętli. Spowoduje to przerwanie pętli i wykonanie instrukcji po catch (jeśli any).

Wszystko zależy od twoich potrzeb. Wolę spróbować...przechwyć wewnątrz pętli podczas wdrażania, ponieważ jeśli wystąpi wyjątek, wyniki nie są niejednoznaczne i pętla nie pęknie i nie zostanie wykonana całkowicie.

 0
Author: Juned Khan Momin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-12-23 14:59:58