Wyjątki - blok try catch
W większości programów, które będziemy mieli okazję napisać w swoim życiu występują błędy różnego rodzaju. Niektóre z nich są spowodowane naszą błędną implementacją, a inne na przykład błędnymi danymi podanymi przez użytkownika. Byłoby więc absurdem, gdybyśmy nie otrzymali od twórców języka Java prostego mechanizmu pozwalającego na efektywną i stosunkowo prostą ich obsługę. Lekcja ta nauczy Cię jak zgłaszać i obsługiwać wyjątki, a następnie spróbujemy także stworzyć własny wyjątek.
Zaczynając od początku. Przypomnijmy sobie lekcję poświęconą tablicom. Zadeklarujmy tablicę o rozmiarze 5, a następnie spróbujmy wyświetlić element o indeksie wskazanym przez użytkownika.
Oczywiście po uruchomieniu program działa poprawnie. W przypadku, gdy podamy wartość z zakresu od 0 do 4 (ponieważ jak pamiętamy indeksy tablicy w Javie numerowane są od 0) otrzymamy oczekiwaną wartość. Jednak, gdy podamy na przykład wartość 5 (typowy błąd popełniany przez początkujących programistów, na szczęście stosunkowo prosty do wykrycia) program się "wysypie" pokazując błąd:
"Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5 at Odczyt.main(Odczyt.java:12)"
Jest to spowodowane oczywiście tym, że komórka tablicy o indeksie 5 w naszym przypadku nawet nie istnieje.
Jak sobie z tym poradzić? Można oczywiście wstawić odpowiednią instrukcję warunkową if, która sprawdzi wprowadzony przez nas argument:
Teraz, program działa poprawnie. W przypadku wprowadzenia indeksu spoza zakresu (na przykład wartości ujemnej, lub tak jak wcześniej wykraczającej poza rozmiar tablicy) zostanie wyświetlony komunikat "Niepoprawna wartość" oraz rozmiar naszej tablicy.
Wyobraźmy sobie teraz sytuację, że niedopuszczalne jest, aby program nagle zakończył swoją pracę, ponieważ dalszy kod powinien się zawsze wykonać. W przypadku, gdy użytkownik wprowadzi zupełnie przypadkowo na przykład literę, lub ciąg znaków, niestety otrzymamy jeszcze bardziej nieprzyjemny błąd niż ostatnio - w tym przypadku InputMismatchException. Niestety tym razem nie jest już tak prosto jak poprzednio prawda?
Cóż czas więc powiedzieć, jak sobie z takim problemem poradzić. Z pomocą przychodzi tutaj blok try catch.
Schematycznie wygląda tak:
Zastosujmy więc najpierw blok try catch zamiast instrukcji if else.
Początek jest taki sam i zrozumiały. Tworzymy tablicę, obiekt Scanner do pobrania danych od użytkownika oraz zmienną index do zapamiętania indeksu (można by ją pominąć).
Następnie w bloku try{ } próbujemy wyświetlić wartość komórki tablicy o indeksie podanym przez użytkownika. Przy wprowadzeniu np. 5 oczywiście wystąpi błąd (bo w javie tablice numerujemy od 0, u nas jest 5 elementów, więc maksymalny indeks wynosi 4), wykonywana jest więc obsługa odpowiedniego wyjątku, a tym przypadku ArrayIndexOutOfBoundsException (można także spróbować przechwycić ogólniejszy wyjątek IndexOutOfBoundsException - działanie byłoby takie samo). Wykonuje się blok catch{ }, w którym zadeklarowano wyjątek odpowiedniego typu i wyświetla się ten sam komunikat co poprzednio.
Wygląda to może mniej czytelnie niż rozwiązanie z instrukcją if, ale trzeba zauważyć, że użyteczność rośnie wraz z rozbudową kodu w bloku try{ } oraz rosnącą ilością sytuacji wyjątkowych, które mogą nastąpić.
Wróćmy więc do problemu wprowadzenia nieodpowiednich danych przez użytkownika. Przyjmijmy, że dopóki użytkownik nie wprowadzi poprawnej wartości to będzie o nią proszony do skutku. Ze względu na specyfikację metody nextInt, zastąpimy ją odczytem danych przy pomocy obiektu BufferedReader, metody readLine() pobierającej od użytkownika String, a następnie dokonamy rzutowania na wartość int przy pomocy statycznej metody parseInt() klasy osłonowej Integer - w tym ostatnim kroku, jeśli zostanie wygenerowany wyjątek typu NumberFormatException poprosimy użytkownika o ponowne podanie wartości. Brzmi może zawile, ale po spojrzeniu na poniższy kod wszystko powinno stać się jasne:
W następnych lekcjach zajmiemy się zgłaszaniem wyjątków w inny sposób oraz tworzeniem własnych klas wyjątków.
Dyskusja i komentarze
Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.
Poniżej znajdziesz archiwalne wpisy z czasów, gdy strona była jeszcze hobbystycznym blogiem.
shpyo
Bardzo dobrze opisane! Dziękuję.
Tomek
Po zapoznaniu się z tą lekcją nasuwa mi sie takie pytanie, kiedy mam korzystać z Scanner, a kiedy z BufferedReader?
Amman
Dwa pytania: 1. Czym są znaki n i e przy rodzaju błędów? 2. Czy można prosić o wyjaśnienie linijki "czyPoprawne = index == -1? false : true;"? Domyślam się jaki ma efekt, ale nie rozumiem w jaki sposób ona działa. Pozdrawiam
Slawek
n, e to po prostu nazwy wyjątku jaki przechwytujemy. Zapis catch (ArrayIndexOutOfBoundsException e) pozwala nam na przechwycenie wyjątku ArrayIndexOutOfBounds, a e to zmienna tego typu, która pozwoli nam wyświetlić informacje o nim. Co do drugiego pytania - faktycznie nie napisałem o tym w lekcjach, będę musiał uzupełnić. Tutaj jest napisane co to takiego: http://javastart.pl/efektywne-programowanie/javatraps-002/ w skrócie, "jeśli index wynosi -1, to przypisz do zmiennej czyPoprawny wartość false, w innym przypadku przypisz wartość true".
Paweł
Dlaczego do zmiennej index przypisujemy -1? Po uruchomieniu programu i wpisaniu w konsole wartości -1 nic sie nie dzieje, program "czeka" na dalsze wpisanie wartości. I w jakim przypadku zostanie zwrócony wyjątek catch (IOException e) { System.out.println("Błąd odczytu danych"); Dzięki za cierpliwość i wcześniejsze odpowiedzi;)
Tomek
Bo gdybyśmy nic nie przypisali to wartość domyślna wynosiłaby 0. Wpisz sobie jako "element tablicy, który chcesz zobaczyć" 0 i sprawdź, co się stanie. Skrypt od razu przechodzi do ostatniego try. PS: Sam się uczę więc poprawcie mnie jeśli się pomylę :)
dakar
Jak ja wywalam -1 i zostawiam tylko int index; to nic to nie zmienia.
Paweł
I jeszcze jedno. try { System.out.println(tab[index-1]); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Niepoprawny parametr, rozmiar tablicy to: "+tab.length); } Jak zrobić żeby instrukcja wykonywała się dopóki poda się właściwy parametr bo coś nie mogę sobie z tym poradzić...
Tomek
Robiłem rozwiązanie dla Ciebie. Nie działa ale może uda Ci się poprawić mój błąd. Jeśli tak to wytłumacz mi co jest źle. 1 import java.io.BufferedReader; 2 import java.io.IOException; 3 mport java.io.InputStreamReader; 4 5 public class Odczyt{ 6 public static void main(String[] args){ 7 int tab[] = {100,200,300,400,500}; 8 BufferedReader odczyt = new BufferedReader(new InputStreamReader(System.in)); 9 int index = -1; 10 11 boolean czyPoprawne = false; 12 13 while(!czyPoprawne) { 14 try { 15 System.out.println("Który element tablicy chcesz zobaczyć: "); 16 index = Integer.parseInt(odczyt.readLine()); 17 System.out.println(tab[index-1]); 18 } catch (NumberFormatException n) { System.out.println("Niepoprawne dane! "); 19 } catch (IOException e) { System.out.println("Błąd odczytu danych"); 20 } catch (ArrayIndexOutOfBoundsException e) { 21 System.out.println("Niepoprawny parametr, rozmiar tablicy to: "+tab.length); 22 } 23 czyPoprawne = index == -1? false : true; 24 } 25 } 26 } Nie wiem dlaczego, jeśli wpiszę np. 12 to trzeci błąd(linijka 20) nie powtarza poleceń try(linijki 15,16,17). Działa na pewno, bo wyświetla komunikat o błędzie(linijka 21).
Slawek
Masz błędny warunek w wyrażeniu warunkowym. Zauważ, że zmienną index zmieniasz z -1 na jakąś wartość niezależnie, czy jest ona poprawna, czy nie. Dodaj w obsłudze wyjątku linijkę index=-1; albo zmień warunek na coś podobnego: czyPoprawne = (index < 0) || (index > tab.length) ? false : true;
TomekA12
Twoja rada poskutkowała. Nie wziąłem pod uwagę, że jeśli podamy niezgodną z typem index'u wartość to ona się nie zmieni z -1. Przyzwyczajenia z PHP ;) Dzięki za pomoc.
Janosch
Sławku, robię tak jak mówisz, ale możesz zobaczyć dlaczego nie wyświetla mi komunikatu w momencie kiedy podaję wartość większą od rozmiaru tabeli? Kod na forum http://forum.javastart.pl/Thread-Wyj%C4%85tki-i-blok-try-catch?pid=1032#pid1032
Witek
Witam Mam pytanie odnośnie sposobu zgłaszanie typu wyjątku np.: catch (ArrayIndexOutOfBoundsException e) Gdy robiłem te ćw. w Eclipsie to przy bloku try { } catch { } dostawałem taki zapis catch(Exception e) sprawdziłem obie opcje i wszystkie ćw. działają tak samo. Czy błędem będzie jeśli będę korzystał z podpowiedzi, która jest w Eclipsie, czy też muszę wpisywać tak jak podałeś w przykłądzie.
Slawek
Eclipse podpowiada najogólniejszy typ, który można obsłużyć. Przykładowo jeżeli w bloku try catch mamy fragmenty, które mogą zgłaszać 3 różne rodzaje wyjątków dziedziczące z Exception, to można je albo przechwycić wspólnie tak jak napisałeś, albo przechwycić każdy z osobna, co pozwala na wyszczególnienie specyficznych sytuacji.
Witek
Witam Dzięki za odpowiedź. Twój post wiele wyjaśnił.
blezus
chyba nie zrozumiałem do końca tego bloku try catch, dlaczego nie mogę go wykorzystać przy "index = odczyt.nextInt();" ? <code> import java.util.Scanner; public class Odczyt { public static void main(String[] args) { int tab[] = {1, 2, 3, 4, 5}; Scanner odczyt = new Scanner(System.in); int index = -1; System.out.println("Podaj indeks tablicy, który chcesz zobaczyć: "); try { index = odczyt.nextInt(); } catch (InputMismatchException e) { System.out.println("Tak nie może być"); } try { System.out.println(tab[index]); } catch (ArrayIndexOutOfBoundsException d) { System.out.println("Niepoprawny parametr"); } System.out.println("Koncowy tekst."); } } </code>
Slawek
Musisz zaimportować klasę wyjątku: <pre type="code" class="java">import java.util.InputMismatchException;</pre>
Janosch
Rozumiem, że jeżeli używamy klasy Scanner to musimy użyć wyjątku: import java.util.InputMismatchException - w celu wychwycenia znaków będących nie cyframi w naszym przykładzie? Czy oprócz tego są jeszcze jakieś różnice pomiędzy zastosowaniem klasy Scanner a klasy BufferedReader do naszego przykładu oprócz stosowania innych klas wyjątków do każdego z nich?
blezus
jeszcze jedna rzecz mnie ciekawi, "index = Integer.parseInt(odczyt.readLine());" - o co tutaj dokładnie chodzi? dlaczego jeśli nie użyję dla tej linii bloku try catch to dostaje błąd że wyjątek dla tej linii musi być zadeklarowany?
Slawek
Wyjątki generalnie dzielimy na dwie grupy, z których jedna to wyjątki, które muszą być obsłużone, a druga to takie, które możemy, ale nie musimy obsługiwać. Te drugie mają taką cechę, ponieważ zazwyczaj albo nie możemy z nimi zrobić nic sensownego, albo dużo lepiej jest poprawić kod tak, aby tym wyjątkom zapobiegać.
noi
a gdzie lekcja z tablicami ?
Ppp
Jakoś tak imho średnio opisany jest ta lekcja. Jeśli dobrze rozumiem, instrukcje w try są "cofane" w przypadku złapania wyjątku? Bo chyba inaczej trój-operator w zasadzie ustawiał by bool'a na true za każdym razem gdy podać coś innego niż -1 :/ ?
DDO
Jak wiesz że coś nie działą to tego nie wykonujesz. Jak wyskoczy błąd(wyjątek) to kod jest nie ważny
odp
mam pytanie co do tego " boolean czyPoprawne = false;" "czyPoprawne = index == -1? false : true;" pętla while wykonywana jest jeśli warunek jest prawdziwy więc warunek boolean zatwierdzony jako fałszywy jest fałszywy to wychodzi na to że jest prawdziwy?
Pav
Co oznacza "!" w 'while(!czyPoprawne) '
Sławek Ludwiczak
! (wykrzyknik) to zaprzeczenie, czyli "dopóki NIE poprawne" wykonuj to co jest w pętli.
Karol
Witam Czy mogłyby mi ktos wytłumaczyc krok po kroku co oznacza zapis czyPoprawne = index == -1? false : true; z góry dziekuje
Krzysztof
A nie możnaby np. jak w C++. czyPoprawne = (index != -1); ?
Damian
Chyba tak, sprawdź. Zapis czyPoprawne = (index == -1)?false : true; funkcjonuje zarówno w C++ jak i w Javie.
DDO
Jak i w PHP :-). Tz. operator trójkowy
Sławek Ludwiczak
to samo co: <pre name='code' class='java'> if(index==-1){ czyPoprawne = false; } else { czyPoprawne = true; } </pre>
Mikios
Nie rozumiem, zastosowania linijki int index = -1; Może mi ktoś wytłumaczyć?
Damian
To jest przyjęcie domyślnie indeksu który na pewno jest poza obszarem tablicy (indeksy tablicy numerowane są liczbami całkowitymi od 0 w górę). Generalnie wartość i tak jest zaraz potem nadpisywana przez "index = odczyt.nextInt();", więc myślę że taki sposób zapisu może być traktowany jako sugestia, że zmienna jest podatna na przyjmowanie różnego rodzaju błędów i warto się o nią jakoś lepiej zatroszczyć. O prawdziwych intencjach takiego zapisu może Cię poinformować tylko autor, ja Cię mogę zapewnić, że wartość zmiennej index w tej linijce nie ma najmniejszego znaczenia, jeśli chodzi o działanie programu :).
grzecho
hmmm. ok rozumiem wiekszosc, ale dlaczego trzeba koniecznie zainicjować wartość index, a nie mozna po prostu zostawic tylko zadeklarowanej zmiennej "int index;"
bf
Cześć, kolejne pytanie. Czy w Javie można skonstruować obsługę wyjątków tak jak w PL/SQL tzn. wymieniasz wyjątki, które chcesz obsłużyć + można użyć identyfikatora OTHERS, czyli po prostu wszystkie inne wyjątki. Z przykładu tutaj przedstawionego wynika, że jeżeli nie trafimy na wymieniony wyjątek do obsłużenia to i tak program przejdzie do następnej części, czyli obsługi kodu bez wyjątków. Pozdrawiam,
Piotrek
Da się. Wystarczy, że dodasz kolejny wyjątek Exception. Wtedy jak wcześniej wyszczególnione nic nie złapią to ten jest najogulniejszy i złapie wszystko co nie zostało wcześniej wyszczególnione. try { // sprawdzana funkcja } catch(/* jakiś wyjątek */) { // obsługa jakiegoś wyjątku }catch(Exception e) { // obsługa wszystkich pozostałych wyjątków }
Janosch
Czyli można to interpretować w ten sposób? try { //sprawdzana funkcja } catch(wyjatek1) {"wystąpił błąd 1"} catch(wyjątek2) {"wystąpił błąd 2"} catch(Exception e) {"wystąpił błąd nieznany"} ??
Arek
Kiedy pętla while wykona tę część kodu? catch (IOException e) { System.out.println("Błąd odczytu danych");
Ignacy
Cześć, wiecie może po co tworzyć int index = -1; skoro można to zrobić od razu w linijce int index = odczyt.nextInt(); ?
Marcin Kunert
Masz rację, twoja wersja też jest poprawna.
Faraen
Witam, mam pytanko, czy nie można by zamiast pisać skomplikowanej linijki: " czyPoprawne = index == -1? false : true " , której nie da się dopasować do każdego programu, nie można by zapisać przypisania wartości true do boolean czyPoprawnie w bloku try, w taki sposób: try { index = Integer.parseInt(odczyt.readLine()); czyPoprawnie = true; // ta linijka zostanie wykonana tylko jeśli index jest przypisany poprawnie } catch ... Czy może mylę się, lub nie jest to uniwersalna zasada?
name
Wydaje mi się, że tak jest trochę prościej: import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class Odczyt{ public static void main(String[] args){ int tab[] = {1,2,3,4,5}; BufferedReader odczyt = new BufferedReader(new InputStreamReader(System.in)); int index; System.out.println("Który element tablicy chcesz zobaczyć: "); boolean czyZle = true; while(czyZle) { try { index = Integer.parseInt(odczyt.readLine()); System.out.println(tab[index-1]); czyZle = false; } catch (NumberFormatException n) { System.out.println("Niepoprawne dane! " + "\n Który element tablicy chcesz zobaczyć? "); } catch (IOException e) { System.out.println("Błąd odczytu danych"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Niepoprawny parametr, rozmiar tablicy to: "+tab.length+"\nPytam ponownie - który element tablicy chciałbyś zobaczyć? "); } } } }
thingness
Dlaczego dalej już nie ma zadanek ?
Janosch
Jeżeli robiłeś poprzednie zadania i używałeś skanerów do wprowadzania danych przez użytkownika to możesz tam pododawać wyjątki, w ten sposób sobie to utrwalisz.
Verteks
W ostatnim listingu jest pewna nieścisłość przy wyświetlaniu wartości na konsolę - zamiast tab(index-1) według mnie powinno być po prostu tab(index). Dlaczego? Ponieważ elementy tablicy liczymy od zero, a nie od jeden, więc gdy ja jako użytkownik piszę, że chce wyświetlić tab[0], to piszę w konsoli zero, a nie jeden. Jeśli moje podejście do sprawy jest mylne - proszę mnie sprostować i wytłumaczyć, dlaczego ma być tak, a nie inaczej.
Verteks
O, to nie jestem człowiekiem w takim razie, bo bez zastanowienia wpisałem 0 i okazało się, że podałem błędną wartość, co mnie bardzo zdziwiło :P Jak najbardziej zgadzam się z tym, co Janosch napisał na temat użytkowników programów komputerowych, a sam chciałem tylko zwrócić fakt na numerowanie elementów tablicy.
Janosch
Myślę, że autorowi chodziło o to, że człowiek liczy intuicyjnie od 1, więc raczej chcąc wyświetlić pierwszy element z tablicy wprowadzi wartość 1 a nie 0. To taki ukłon programisty do użytkownika, żeby program był jak najbardziej intuicyjny w użytkowaniu.. Bo programy są pisane dla normalnych użytkowników, których nie musi interesować i nie muszą wcale wiedzieć, że Java liczy od zera.
Janosch
Sławku albo Dawidzie. Czy możecie mi powiedzieć, czy poprawne jest umieszczenie warunku końca pętli w bloku try? Coś takiego: try { zmienna = Integer.parseInt(input.readLine()); czyPoprawne = true; System.out.println("Wprowadziłeś zmienną: " + zmienna); } catch(NumberFormatException e) {System.out.println("Wprowadź poprawnie liczbę: ");} catch(IOException f){System.out.println("IOException");} catch(Exception g){System.out.println("Inny błąd");} }
Sławek Ludwiczak
Przechwytywanie Exception prawie nigdy nie ma sensu, chyba, że nie chce Ci się przechwytywać i obsługiwać 10 wyjątków różnego typu (np. gdy piszesz jakiś prosty skrypt, z którego nikt inny nie będzie korzystał). W przykładzie, który podałeś blok z Exception nie ma prawa się wykonać, bo nic wewnątrz bloku try nie wygeneruje wyjątku ogólniejszego niż NumberFormatException lub IOException, a te przechwytujesz wcześniej
Rafal
Czy w Javie jest cos takiego jak TryParse
lolo
A co to jest?
Rafal
Kiedy metoda parse sie nie uda, nie wyrzuca ci wyjatku
lolo
Z tego co się orientuję to w standardzie niema takiej funkcji ale łatwo można napisać sobie taką funkcję. A poza tym chyba dobrze jak zwróci wyjątek który można obsłużyć.
aleksanderwiel
Ktoś może się orientuje, jak się nazywają wyjątki, np. - gdy do zmiennej "int" będziemy chcieli przypisać jakiś tekst - gdy podamy złą liczbę, która jest typu "double" w zmiennej "int" - gdy podamy liczbę wykraczającą poza zakres danej zmiennej przykładowo "int", a zakresem jest liczba mieszcząca się pomiędzy -2 147 483 648, a 2 147 483 648 ?