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 treść metody, której jedynym celem jest zwrócenie tablicy liczb zmiennoprzecinkowych. Chcę zwrócić tę metodę, null
jeśli wystąpi błąd, więc umieszczam pętlę w try...catch
bloku, tak jak to:
try {
for (int i = 0; i < max; i++) {
String myString = ...;
float myNum = Float.parseFloat(myString);
myFloats[i] = myNum;
}
} catch (NumberFormatException ex) {
return null;
}
Ale potem pomyślałem również o umieszczeniu try...catch
bloku w pętli, w ten sposób:
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 preferować jedno od drugiego?
Edycja: Wydaje się, że konsensus polega na tym, że łatwiej jest umieścić pętlę wewnątrz try / catch, prawdopodobnie w ramach własnej metody. Nadal jednak trwa debata, która jest szybsza. Czy ktoś może to przetestować i wrócić z jednolitą odpowiedzią?
java
performance
loops
try-catch
Michael Myers
źródło
źródło
Odpowiedzi:
WYSTĘP:
Nie ma absolutnie żadnej różnicy wydajności w tym, gdzie umieszczone są struktury try / catch. Wewnętrznie są one implementowane 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 nastą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 mniej więcej w połowie.
źródło
Wydajność : jak powiedział Jeffrey w odpowiedzi, w Javie nie ma to większego znaczenia.
Zasadniczo , dla czytelności kodu, wybór miejsca, w którym można przechwycić wyjątek, zależy od tego, czy pętla ma być przetwarzana, czy nie.
W twoim przykładzie wróciłeś po złapaniu wyjątku. W takim przypadku umieściłem try / catch wokół pętli. Jeśli chcesz po prostu złapać złą wartość, ale kontynuować przetwarzanie, włóż ją do środka.
Trzeci sposób : zawsze możesz napisać własną statyczną metodę ParseFloat i obsługiwać wyjątki w tej metodzie zamiast w pętli. Uczynienie obsługi wyjątków izolowanym dla samej pętli!
źródło
W porządku, po tym jak Jeffrey L. Whitledge powiedział, że nie ma różnicy w wydajności (od 1997), poszedłem i przetestowałem to. Przeprowadziłem ten mały test porównawczy:
Sprawdziłem wynikowy kod bajtowy za pomocą javap, aby upewnić się, że nic nie zostało wstawione.
Wyniki pokazały, że przy niewielkich optymalizacjach JIT Jeffrey ma rację ; absolutnie nie ma różnicy w wydajności na Javie 6, VM klienta Sun (nie miałem dostępu do innych wersji). Łączna różnica czasu jest rzędu kilku milisekund w całym teście.
Dlatego jedynym czynnikiem, który wygląda najczystiej. Uważam, że drugi sposób jest brzydki, więc trzymam się albo pierwszego, albo Raya Hayesa .
źródło
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ź następujący przykład:
Pętla while znajduje się w bloku try catch, zmienna „j” jest zwiększana, aż osiągnie 40, drukowana, gdy j mod 4 wynosi zero, a wyjątek jest zgłaszany, gdy j osiągnie 20.
Przed szczegółami oto inny przykład:
Ta sama logika jak powyżej, z tą różnicą, że blok try / catch znajduje się teraz w pętli while.
Oto wyjście (podczas try / catch):
I inne wyjście (spróbuj / catch in while):
Tutaj masz dość znaczącą różnicę:
podczas try / catch wypada z pętli
spróbuj / złap w czasie, gdy pętla będzie aktywna
źródło
try{} catch() {}
pętlę, jeśli pętla jest traktowana jako pojedyncza „jednostka”, a znalezienie problemu w tej jednostce powoduje, że cała „jednostka” (lub pętla w tym przypadku) idzie na południe. Umieśćtry{} catch() {}
wewnątrz pętli, jeśli traktujesz pojedynczą iterację pętli lub pojedynczy element w tablicy jako całą „jednostkę”. Jeśli wystąpi wyjątek w tej „jednostce”, pomijamy ten element, a następnie przechodzimy do następnej iteracji pętli.Zgadzam się ze wszystkimi postami dotyczącymi wydajności i czytelności. Są jednak przypadki, w których to naprawdę ma znaczenie. Kilka innych osób wspomniało o tym, ale może to być łatwiejsze z przykładami.
Rozważ ten nieco zmodyfikowany przykład:
Jeśli chcesz, aby metoda parseAll () zwróciła null, jeśli wystąpią jakiekolwiek błędy (jak w oryginalnym przykładzie), umieść try / catch na zewnątrz w następujący sposób:
W rzeczywistości powinieneś prawdopodobnie zwrócić tutaj błąd zamiast wartości zerowej i ogólnie nie lubię mieć wielu zwrotów, ale masz pomysł.
Z drugiej strony, jeśli chcesz po prostu zignorować problemy i przeanalizować dowolne ciągi znaków, umieść try / catch wewnątrz pętli w następujący sposób:
źródło
Jak już wspomniano, wydajność jest taka sama. Jednak wrażenia użytkownika niekoniecznie są identyczne. W pierwszym przypadku szybko zawiedziesz (tj. Po pierwszym błędzie), jednak jeśli umieścisz blok try / catch w pętli, możesz przechwycić wszystkie błędy, które powstałyby dla danego wywołania metody Podczas analizowania tablicy wartości z ciągów, w których spodziewane są pewne błędy formatowania, zdecydowanie są przypadki, w których chciałbyś być w stanie przedstawić wszystkie błędy użytkownikowi, aby nie musiał próbować ich naprawiać jeden po drugim. .
źródło
Jeśli nie powiedzie się wszystko, to pierwszy format ma sens. Jeśli chcesz móc przetwarzać / zwracać wszystkie elementy, które nie zawiodły, musisz użyć drugiego formularza. To byłyby moje podstawowe kryteria wyboru między metodami. Osobiście, jeśli to wszystko albo nic, nie użyłbym drugiej formy.
źródło
Dopóki masz świadomość 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ę natychmiast po wystąpieniu wyjątku i nie zawsze może być to, czego chcesz. Jest to w rzeczywistości bardzo częsty błąd w oprogramowaniu Java. 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ą również obsługiwać tylko określony wyjątek w pętli i nie oczekiwać żadnego innego wyjątku. Następnie, jeśli wystąpi wyjątek, który nie jest obsługiwany wewnątrz pętli, wówczas pętla zostanie „wstępnie zaprogramowana”, kończy się prawdopodobnie przedwcześnie, a zewnętrzna instrukcja catch obsługuje wyjątek.
Gdyby pętla miała za zadanie opróżnić kolejkę, wówczas najprawdopodobniej pętla mogłaby się zakończyć, zanim ta kolejka zostanie naprawdę opróżniona. Bardzo częsta wina.
źródło
W twoich przykładach nie ma funkcjonalnej różnicy. Uważam, że twój pierwszy przykład jest bardziej czytelny.
źródło
Powinieneś preferować wersję zewnętrzną niż wewnętrzną. To tylko konkretna wersja reguły, przenieś wszystko poza pętlę, którą 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 parametry wydajności.
Z drugiej strony powinieneś prawdopodobnie spojrzeć na float.TryParse lub Convert.ToFloat.
źródło
Jeśli umieścisz try / catch w pętli, będziesz zapętlać po wyjątku. Jeśli umieścisz go poza pętlą, zatrzymasz się, gdy tylko zostanie zgłoszony wyjątek.
źródło
Moim zdaniem, bloki try / catch są niezbędne, aby zapewnić odpowiednią obsługę 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 w pętlach. Ponadto wydaje się, że tam, gdzie występuje ten warunek, często jest wychwytywany „Wyjątek” lub „RuntimeException”. Należy unikać przechwycenia wyjątku RuntimeException w kodzie. Ponownie, jeśli pracujesz w dużej firmie, konieczne jest prawidłowe zarejestrowanie tego wyjątku lub zatrzymanie wyjątku środowiska wykonawczego. Cały punkt tego opisu jest
PLEASE AVOID USING TRY-CATCH BLOCKS IN LOOPS
źródło
ustawienie specjalnej ramki stosu dla try / catch powoduje dodatkowe obciążenie, 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 nieistotna.
Jednak zgadzam się z innymi, że umieszczenie go poza pętlą sprawia, że korpus pętli wygląda na czystszy.
Jeśli istnieje szansa, że kiedykolwiek będziesz chciał kontynuować przetwarzanie zamiast wyjść, jeśli jest niepoprawny numer, wtedy kod powinien znajdować się w pętli.
źródło
Jeśli jest w środku, zyskasz narzut struktury struktury try / catch N razy, a nie tylko raz na zewnątrz.
Za każdym razem, gdy wywoływana jest struktura Try / Catch, nakłada się to na wykonanie metody. Wystarczy odrobina pamięci i tików procesora potrzebnych do poradzenia sobie ze strukturą. Jeśli uruchamiasz pętlę 100 razy, i dla hipotetycznej przyczyny, powiedzmy, że koszt to 1 tyknięcie za próbę / złapanie połączenia, wtedy posiadanie Try / Catch w pętli kosztuje 100 tyknięć, w przeciwieństwie do tylko 1 tyknięcia, jeśli jest to poza pętlą.
źródło
Chodzi przede wszystkim o to, by zachęcić do pierwszego stylu: umożliwienia skonsolidowania obsługi błędów i obsługi tylko raz, a nie natychmiast w każdym możliwym miejscu błędu.
źródło
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 powie ci już złą wartość, ale zasadą jest umieszczenie wszystkich pomocnych danych w zgłaszanych wyjątkach. Pomyśl o tym, co byłoby dla ciebie interesujące w debuggerze w tym momencie programu.
Rozważać:
W razie potrzeby naprawdę docenisz taki wyjątek z jak największą ilością informacji.
źródło
Chciałbym dodać własne
0.02c
o dwóch konkurencyjnych zagadnieniach, gdy patrzę na ogólny problem miejsca, w którym należy ustawić obsługę wyjątków:„Szersza” odpowiedzialność
try-catch
bloku (tj. Poza twoją pętlą w twoim przypadku) oznacza, że zmieniając kod w pewnym późniejszym momencie, możesz przez pomyłkę dodać linię obsługiwaną przez istniejącycatch
blok; być może nieumyślnie. W twoim przypadku jest to mniej prawdopodobne, ponieważ wyraźnie łapieszNumberFormatException
Im „węższa” odpowiedzialność
try-catch
bloku, tym trudniejsze staje się refaktoryzowanie. Szczególnie, gdy (jak w twoim przypadku) wykonujesz instrukcję „nielokalną” z poziomucatch
bloku (return null
instrukcji).źródło
To zależy od obsługi awarii. Jeśli chcesz tylko pominąć elementy błędu, spróbuj w środku:
W każdym innym przypadku wolałbym spróbować na zewnątrz. Kod jest bardziej czytelny, bardziej przejrzysty. Może lepiej byłoby zgłosić wyjątek IllegalArgumentException w przypadku błędu, jeśli zwraca wartość null.
źródło
Wrzucę moje 0,02 $. Czasami kończy się konieczność dodania „w końcu” później w kodzie (ponieważ kto kiedykolwiek pisze swój kod doskonale za pierwszym razem?). W takich przypadkach nagle sensowniej jest spróbować try / catch poza pętlą. Na przykład:
Ponieważ jeśli pojawi się błąd, czy nie, chcesz tylko raz zwolnić połączenie z bazą danych (lub wybrać ulubiony typ innego zasobu ...).
źródło
Innym aspektem nie wymienionym powyżej jest fakt, że każdy try-catch ma trochę wpływ na stos, co może mieć wpływ na metody rekurencyjne.
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 „awarii stosu”, którego używamy w klasie wydajności, kończy się niepowodzeniem przy około 6400 klatkach, gdy try-catch jest w metodzie wewnętrznej, i przy około 11,600, gdy jest w metodzie zewnętrznej.
W prawdziwym świecie może to stanowić problem, jeśli używasz wzoru złożonego i masz duże, złożone struktury zagnieżdżone.
źródło
Jeśli chcesz uchwycić wyjątek dla każdej iteracji lub sprawdzić, co to jest wyjątek generowany i złapać wszystkie wyjątki w itertaion, umieść try ... catch wewnątrz pętli. Nie spowoduje to przerwania pętli, jeśli wystąpi wyjątek i można przechwycić każdy wyjątek w każdej iteracji w całej pętli.
Jeśli chcesz przerwać pętlę i zbadać wyjątek za każdym razem, gdy zostanie zgłoszony, użyj try ... catch out of the loop. Spowoduje to przerwanie pętli i wykonanie instrukcji po złapaniu (jeśli występuje).
Wszystko zależy od twoich potrzeb. Wolę używać try ... catch wewnątrz pętli podczas wdrażania, ponieważ jeśli wystąpi wyjątek, wyniki nie są niejednoznaczne, a pętla nie ulegnie uszkodzeniu i nie wykona się całkowicie.
źródło