Obsługa wyjątków
Obsługa wyjątków przez maszynę wirtualną Javy
Program zbudowany jest z trzech plików: znanych z poprzedniego przykładu interfejsów i programu je wykorzystującemu. W następnych przykładach dotyczących wyjątków te interfejsy będą wykorzystywane.
Interfejs pierwszy.
Interfejs drugi.
Program:
P24Wyjatki.java
Warto zwrócić uwagę, że korzystanie z interfejsu zapisanego w tym samym katalogu nie wymaga umieszczenia słowa kluczowego implements. Kompilacja programu, w którym znajdują się odwołania do klas czy też interfesów z tego samego katalogu pociąga za sobą kompilację wszystkich niezbędnych plików.
Zgłoszenie wyjątku to utworzenie obiektu wyjątku za pomocą operatora new i odłożenie go na stercie - tzn w zwykły dla Javy sposób,
Scieżka aktualna programu, tzn. ta która w danym momencie nie może się wykonać, jest przerywana, a obiekt wyjątku jest wyrzucany z aktualnego kontekstu,
Sterowanie przejmuje mechanizm obsługi wyjątku - wyjątek jest przechwytywany i obsługiwany
Jedną z zalet takiego podejścia jest możliwość skoncentrowania się na rozwiązaniu zadania w jednym miejscu a możliwymi powstającymi sytuacjami wyjątkowymi można się zająć oddzielnie
Do obsługi sytuacji wyjątkowych można podchodzić na dwa sposoby
Kończenie
wznawianie po naprawieniu
Procedury obsługi wyjątków (te w bibliotekach) stosują tę pierwszą metodę.
Programista może przechwytywać wyjątki i obsługiwać je w sposób jemu przydatny - i to będzie tematem następnego przykładu.
Przechwytywanie wyjątków
Przyjrzyjmy się programowi i wynikom jego wykonania wykonania. Zakładamy, że są dołączone te same interfejsy co w przykładzie poprzednim.
P25Wyjatki.java
Obszar chroniony to fragment kodu, w którym mogą być produkowane obiekty - wyjątki, za takim obszarem musi być umieszczony kod obsługujący te wyjątki
Blok try nazywany blokiem prób służy do przechwytywania produkowanych wyjątków, w bloku tym próbujemy wykonać fragment kodu, ma on postać:
try {
// tu należy umieścić kod mogący produkować wyjątki
}
W umieszczonym fragmencie kodu mogą być produkowane różne rodzaje wyjątków, każdy z nich musi mieć swoją obsługę wyjątkowej sytuacji.
Procedury obsługi wyjątków są umieszczane bezpośrednio po bloku try - i są oznaczone słowem kluczowym catch, argumentem procedury catch jest obsługiwany przez nią wyjątek. Ogólna postać bloku prób wraz z wieloma procedurami obsługi wyjątków jest następująca:
try {
// kod mogący produkować wyjątki
}
catch(KlasaWyjątku_1 identyfikator_1) {
// obsługa wyjątku
}
catch(KlasaWyjątku_2 identyfikator_2) {
// obsługa wyjątku
}
Przechwytywany i obsługiwany jest tylko ten wyjątek, który został wyrzucony - pozostałe są pomijane. Umieszczenie powtórne obsługi tego samego wyjątku jest błędem kompilacji.
Tworzenie i wyrzucanie wyjątków, dołączanie wyjątków
Prześledzimy wykonanie programu dla różnych danych wejściowych. Tak jak i uprzednio niezbędne są obydwa interfejsy.
P26Wyjatki.java
Można tworzyć własne wyjątki. Wyjątek jest klasą. Wyjątek powinien dziedziczyć po istniejącym wyjątku. Dobrze jest by dziedziczył po zbliżonym do tworzonego wyjątku. W tym przykładzie utworzony jest najprostszy z możliwych wyjątków.
Wyjątek dziedziczy ZlyMiesiacDzien po klasie Exception.
Klasa Exception ma dwa konstruktory: konstruktor bezargumentowy oraz konstruktor z jednym argumentem klasy String.
W klasie ZlyMiesiacDzien nie jest utworzony żaden konstruktor. Można zatem korzystać wyłącznie z domyślnego konstruktora domyślnego.
Wyrzucenie wyjątku polega na utworzeniu obiektu klasy wyjątku i wyrzucenie (throw) go. Wyrzucanie wyjątków ma więc postać:
throw new NazwaWyjątku(lista_argumentów_konstruktora)
Wyrzucanie wyjątków znajduje się w metodzie dajZnak (linie 11 i 14). Definicja metody, w której wyrzucane są wyjątki oraz metody, w której są wykonywane metody wyrzucające wyjątki musi mieć w swoim nagłówku zawartą informację o tym. Powinna wyglądać następująco:
[static][modyfikator_dostępu]TypKlasa nazwaMetody(argumenty) throws Wyjatek_1, Wyjatek_2, ... {
ciało metody
}
Taką postać ma metoda dajZnak() oraz metoda main(). W metodzie main dołączona jest obsługa dwóch rodzajów wyjątków.
W klasie P26Wyjatki są implementowne dwa interfejsy: MiesiacDzien, TablicaZodiak. W tym przypadku nie jest to niezbędne - interfesy znajdują się w tej samej kartotece, niemniej warto pamiętać o konieczności w większości implementacji.
Przechwytywanie wyjątków, ponowne wyrzucanie wyjątków
Następny, poniższy program, dla różnych danych wejściowych pozwoli na zilustrowanie wymienionego w tytule problemu (zauważ, że tym razem metoda dajZnak() nie jest metodą statyczną).
P27Wyjatki.java
Zdarza się, że przejmując wyjątek jesteśmy w stanie naprawić błąd z powodu, którego powstał - program może wznowić działanie - tak było w jednym z poprzednich przykładów.
Możemy natrafić na sytuację, w której naprawa systemu nie powiedzie się - można ponownie wyrzucić ten sam wyjątek. Takie ponowne wyrzucenie wyjątku jest umieszczone w procedurach obsługi wyjątków catch i ma ono postać throw obiekt_wyjątku;
Zauważmy, że zdefiniowana przez nas klasa ZlyMiesiacDzien wymusza jednakowy komunikat zarówno wtedy, gdy błąd powstał z powodu wprowadzenia złej liczby jako numer miesiąca jak i numer dnia. Sytuację można zmienić budując inne konstruktory - inne niż domyślne.
Zwróć uwagę, że w tym przykładzie metoda dajZnak() nie jest metodą statyczną, co znacza, że można się do niej odwołać wyłacznie wtedy, gdy obiekt klasy P27Wyjatki zostanie utworzony. Konieczna jest zatem instrukcja tworząca taki obiekt. Zauważ również, że obiekt o nazwie x jest tworzony za pomocą konstruktora domyślnego.
Metoda dajZnak() jest wykonywana na rzecz obiektu x, a nie bezpośrednio jak w przypadku poprzednim.
Wywołanie System.err.println(). Obiekt System.err odpowiada standardowemu strumieniowi błędów.
Każda klasa zapisana w pliku typu *.java tworzy plik nazwa_i.class. Na podstawie jednego pliku typu *.java może być utworzonych wiele plików typu nazwa_i.class. Zapisując pliki w jednej kartotece należy pamiętać o tej zasadzie. Nie można nadawać tych samych nazw klasom działającym inaczej.
W tym i poprzednich przykładach mamy dwie klasy. Jedna z nich ma tę samą nazwę we wszystkich poprzednich przykładach. Ma również tę samą treść - nie ma więc sprzeczności.
Tworzenie wyjątków z innymi niż domyślne, konstruktorami
Prześledzimy wykonanie następnego programu dla kilku zestawów danych.
P29Wyjatki.java
Budowana jest nowa klasa wyjątku o nazwie ZlyMiesiacDzienZnak. Klasa wyjątku, różni się od poprzednio zbudowanej tym, że jest uzupełniona konstruktorem z argumentem w postaci łańcucha tekstowego.
Klasa ZlyMiesiacDzienZnak dziedzicząca po podstawowej klasie obsługującej wyjątki, pozwala na dokładniejszą informację o rodzaju popełnionego błędu. Uzyskuje się to przez przeładowanie (czytaj: ponowne zdefiniowanie) konstruktora klasy bazowej z jednym argumentem w postaci łańcucha tekstowego. W konstruktorze tym wołany jest konstruktor klasy bazowej super(tekst).
Uzupełnienie wiadomości o wyjątkach
Na zakończenie rozważań o wyjątkach, jeszcze jeden program i kilka uzupełnień. Program wykonany będzie dla kilku danych wejściowych.
P30Wyjatki.java
Sekcja finally
Sekcja finally. W obszarze chronionyn przez try oprócz sekcji catch może wystąpić finally - blok chroniony może mieć więc postać taką jak przedstawina poniżej. Sekcja finally musi wystąpić po wszystkich procedurach catch.
try {
// kod mogący produkować wyjątki
} catch(KlasaWyjątku_1 identyfikator_1) {
// obsługa wyjątku
} catch(KlasaWyjątku_2 identyfikator_2) {
// obsługa wyjątku
}
finally {
// tu kod, który wykona się zawsze
}
W przykładzie sekcja finally pełni jedynie rolę ilustracyjną.
Sekcja finally wykona się bez względu na to czy w kodzie chronionym zostanie wyrzucony wyjątek, czy też nie. Sekcja finally jest konieczna, kiedy trzeba przywrócić do pierwotnego stanu coś innego niż pamięć; może być to otwarty plik, połączenie sieciowe, obrazek na ekranie, zapewnienie wybranego stanu zmiennej itp. bez względu na sposób wykonania obszaru chronionego.
Każdy budowany przez nas wyjątek dziedziczy po innej klasie wyjątków, w szczególności np. po klasie Exception. Hierarchia klas wyjątków musi być uwzględniona w kolejności umieszczania procedur catch. Procedura catch zapisana w komentarzu nie może wystąpić w tym miejscu, ponieważ nigdy nie będzie wykonana. Klasa Exception stoi wyżej w hierarchii klas i to ona przejmie wyjątek. Wyjątki są przejmowane przez pierwszą pasującą procedurę wyjątku.
Bład wynikający z wprowadzenia tekstu wtorek zostanie obsłużony przez NumberFormatException, natomiast wyjątek wyrzucony po wprowadzeniu wartości 2 zamiast tekstu wtorek jest obsłużony przez Exception. Chcąc, by wyjątek ten został obsłużony przez BardzoZlyMiesiacDzienZnak należy odpowiednią procedurę catch z argumentem klasy BardzoZlyMiesiacDzienZnak przed procedurą catch z argumentem klasy Exception.
Warto zwrócić uwagę na dołączane klasy wyjątków do metody main() jest tam tylko wyjątek klasy Exception. Tak samo do metody dajZnak() można dołączyć wyjątki klasy Exception. Jest to oczywiście pewien nadmiar, ale zwalnia nas to od znajomości nazw wyjątków o ile sytuacji wyjątkowych nie chcemy obsługiwać uwzględniając typ wyjątku.
Klasa wyjątków RuntimeException
Wyjątki klasy RuntimeException są automatycznie obsługiwane przez Javę i ich obsługa nie musi być prowadzona przez programistę. W pierwszym przykładzie na temat wyjątków widzieliśmy ilustrację tegoż.
Takim wyjątkiem jest ArrayIndexOutOfBoundsException,NullPointerExeption, ArithmeticException: / by zero
Wyjątki tej klasy zawierają błędy programistyczne, takie jak odwołanie do zerowej referencji, dzielenie przez 0, przekroczenie zakresu tablic itp.