4. Wyjątki
Jak wiemy, programowanie w Javie sprowadza się tak naprawdę do tworzenia
obiektów i wywoływania na ich rzecz metod (albo też - gdy nie mamy obiektów
- wywoływania metod statycznych). W trakcie wykonania metody może powstać
jakiś błąd. Tradycyjna obsługa błędów (a raczej ich sygnalizowanie) polegało na:
Problemy, które występują przy takim podejściu:
W Javie (poszerzającej doświadczenia języka C++) zaproponowany nowy sposób obsługi Wyjątek - to sygnał o błędzie w trakcie wykonania programu
Wyjątek powstaje na skutek jakiegoś nieoczekiwanego błędu.
Prosty schemat obsługi wyjątków
try { // ... w bloku try ujmujemy instrukcje, które mogą spowodować wyjątek } catch(TypWyjątku exc) { // ... w klauzuli catch umieszczamy obsługę wyjątku } Gdy w wyniku wykonania instrukcji w bloku try powstanie wyjątek typu TypWyjątku to sterowanie zostanie przekazane do kodu umieszczonego w w/w klauzuli catch
Przykłady. public class NoCatch { public static void main(String[] args) { int a = 1, b = 0, c = 0; c = a/b; System.out.println(c); } }
Exception in thread "main" java.lang.ArithmeticException: / by zero
at NoCatch.main(NoCatch.java:6)
b) Zabezpieczamy się przed możliwymi skutkami całkowitoliczbowego dzielenia przez zero, obsługując wyjątek ArithmeticException public class Catch1 { public static void main(String[] args) { int a = 1, b = 0, c = 0; String wynik; try { c = a/b; wynik = "" + c; } catch (ArithmeticException exc) { wynik = "***"; } System.out.println(wynik); } }
W tym przypadku, wykonanie instrukcji c = a/b; spowoduje powstanie wyjątku
(dzielenie przez zero), a ponieważ instrukcja ta znajduje się w bloku try,
do którego "podczepiona" jest klauzula catch z odpowiednim typem wyjątku,
to sterowanie zostanie przekazane do kodu w catch, zmienna wynik uzyska wartość
"***", i wynik ten zostanie wyprowadzony na konsolę. Gdyby zmienna b nie
miała wartości zero, wyjątek by nie powstał, kod w klauzuli catch nie został
by wykonany i na konsolę wyprowadzony by został wynik dzielenia a/b. import java.util.*; import javax.swing.*; public class Oper { public static void main(String[] args) { String normalQuest = "Liczba1 op Liczba2", errorQuest = "Wadliwe dane. Jeszcze raz.\n" + normalQuest, quest = normalQuest; String expr; int num1 = 0, num2 = 0, res = 0; while ((expr = JOptionPane.showInputDialog(quest)) != null) { StringTokenizer st = new StringTokenizer(expr); if (st.countTokens() != 3) { quest = errorQuest; continue; } String snum1 = st.nextToken(), sop = st.nextToken(), snum2 = st.nextToken(); try { num1 = Integer.parseInt(snum1); num2 = Integer.parseInt(snum2); } catch (NumberFormatException exc) { quest = errorQuest; continue; } char op = sop.charAt(0); switch (op) { case '+' : res = num1 + num2; break; case '-' : res = num1 - num2; break; case '*' : res = num1 * num2; break; case '/' : res = num1 / num2; break; default: { quest = errorQuest; continue; } } JOptionPane.showMessageDialog(null, "Wynik = " + res); quest = normalQuest; } System.exit(0); } }
A cóż to jest NumberFormatException albo ArithmeticExcception? I dlaczego
w klauzuli catch używamy takich nazw z dodatkiem czegoś, co wygląda jak zmienna np. catch (NumberFormatException exc) ... ![]() (Żródło: Peter Haggar, Java Exception Handling, IBM 1999) Zatem nazwy NumberFormatException, ArithmeticException itd. sa nazwami klas, a zmienna exc we wczesniejszych przykładach jest faktycznie zmienną - zawiera referencję do obiektu odpowiedniej klasy wyjątku. Wobec tej zmiennej możemy np. użyć metody toString() uzyskując jako wynik
jej zastosowania opis wyjątku, taki jaki daje JVM, gdy wyjątek jest nieobsługiwany.
WYJĄTKI KONTROLOWANE I NIEKONTROLOWANE
Wiele razy natkniemy się na sytuację, w której musimy obslugiwać wyjątki, które mogą powstać przy wywołaniau jakichś metod ze standardowych klas Javy. Jeśli tego nie zrobimy, kompilator wykaże błąd w programie. Sytuacja taka dotyczy, na przykład, metod ze standardowego pakietu java.io, zawierającego klasy do operowania na strumieniach danych (m.in. plikach). Przykład (jeśli okaże się niezrozumiały, proszę wrócić do niego po lekturze następnego punktu - o plikach) :
String inFname = ...; // nazwa pliku wejściowego Gdybyśmy napisali metodę kopiującą strumienie i nie obsługiwali w niej wyjątków wejścia-wyjścia - to musielibyśmy zaznaczyć, że przy wywołanie takiej metody moga powstać wyjątki klasy IOException: public static void copyStream(InputStream in, OutputStream out)
int c = 0;
a obsługa wyjątku IOException, który może powstać przy wywołaniu read() musiałaby być prowadzona w miejscu wywołania metody copyStream(...): try {
SEKWENCJA DZIAŁANIA try-catch
Klauzula finally służy do wykonania kodu niezależnie od tego czy wystąpił wyjątek czy nie. boolean metoda(...) {
Jeśli powstał wyjątek - wykonywana jest klauzula catch.
WŁASNE WYJĄTKI Wyjątki są obiektami klas pochodnych od Throwable.
class NaszWyj extends Exception {
Zwykle w naszej klasie wystarczy umieścić dwa konstruktory: bezparametrowy oraz z jednym argumentem typu String (komunikat o przyczynie powstania wyjątku). W konstruktorach tych nalezy wywołać konstruktor nadklasy (za pomocą odwołania super(...), w drugim przypadku z argumentem String). Użycie wyjątku:
Poniższy przykład ilustruje wyżej powiedziane. import javax.swing.*; class NotValidZipException extends Exception { // Klasa wyjątku NotValidZipException() { super(); } NotValidZipException(String s) { super(s+ "\nPoprawny kod ma postać: nn-nnn"); } } public class ZipAsk { public ZipAsk() { } public String getZip() throws NotValidZipException { final int N = 6, // długość kodu P = 2; // pozycja na której występuje kreska String zip = JOptionPane.showInputDialog("Podaj kod pocztowy:"); if (zip == null) return zip; boolean valid = true; // czy kod poprawny? char[] c = zip.toCharArray(); // tablica znaków w podanym kodzie // jeżeli struktura wadliwa: nie ta długość, brak kreski if (c.length != N || c[P] != '-') valid = false; // czy w kodzie występują tylko cyfry? for (int i = 0; i<N && valid; i++) { if (i==P) continue; if (!Character.isDigit(c[i])) valid = false; } // w tej chwili wiemy już, czy kod jest poprawny // jeśli nie: // - tworzymy i zgłaszamy wyjątek if (!valid) throw new NotValidZipException("Wadliwy kod: " + zip); // w przeciwnym razie zwracamy kod return zip; } } class ZipAskTest { public static void main(String[] args) { JOptionPane.showMessageDialog(null, "Podaj trzy prawidłowe kody pocztowe"); ZipAsk zask = new ZipAsk(); String zip = null; int n = 3; while (n > 0) { try { zip = zask.getZip(); if (zip == null) break; n--; } catch (NotValidZipException exc) { JOptionPane.showMessageDialog(null, exc.getMessage()); continue; } System.out.println("Kod " + (3-n) + " : " + zip); } System.exit(0); } }
|