1. Rodzaje wyjątków

Wszystkie wyjątki są podzielone na 4 typy, które w rzeczywistości są klasami, które dziedziczą się nawzajem.
Throwable
klasa
Klasą bazową dla wszystkich wyjątków jest Throwable
klasa. Klasa Throwable
zawiera kod, który zapisuje bieżący stos wywołań (śledzenie stosu bieżącej metody) do tablicy. Dowiemy się, czym jest ślad stosu nieco później.
Operator throw może zaakceptować tylko obiekt wywodzący się z Throwable
klasy. I chociaż teoretycznie możesz pisać kod taki jak throw new Throwable();
, nikt zwykle tego nie robi. Głównym celem klasy Throwable
jest posiadanie jednej klasy nadrzędnej dla wszystkich wyjątków.
Error
klasa
Następną klasą wyjątków jest Error
klasa, która bezpośrednio dziedziczy Throwable
klasę. Maszyna Java tworzy obiekty klasy Error
(i jej potomków), gdy wystąpią poważne problemy . Na przykład awaria sprzętu, niewystarczająca ilość pamięci itp.
Zwykle jako programista nic nie możesz zrobićError
w sytuacji, gdy w programie wystąpił taki błąd (taki, za jaki należy wyrzucić an ): te błędy są zbyt poważne. Wszystko, co możesz zrobić, to powiadomić użytkownika, że program się zawiesza i/lub zapisać wszystkie znane informacje o błędzie do dziennika programu.
Exception
klasa
Klasy Exception
i RuntimeException
dotyczą typowych błędów, które zdarzają się podczas działania wielu metod. Celem każdego zgłoszonego wyjątku jest złapanie go przez catch
blok, który wie, jak prawidłowo go obsłużyć.
Gdy metoda z jakiegoś powodu nie może zakończyć swojej pracy, powinna natychmiast powiadomić metodę wywołującą, zgłaszając wyjątek odpowiedniego typu.
Innymi słowy, jeśli zmienna jest równa null
, metoda zgłosi NullPointerException
. Jeśli do metody zostaną przekazane niepoprawne argumenty, zgłosi ona błąd InvalidArgumentException
. Jeśli metoda przypadkowo podzieli się przez zero, zgłosi błąd ArithmeticException
.
RuntimeException
klasa
RuntimeExceptions
są podzbiorem Exceptions
. Można nawet powiedzieć, że RuntimeException
jest to lżejsza wersja zwykłych wyjątków ( Exception
) — na takie wyjątki nakłada się mniej wymagań i ograniczeń
Poznasz różnicę między Exception
a RuntimeException
później.
2. Throws
: sprawdzone wyjątki
Wszystkie wyjątki Java dzielą się na 2 kategorie: zaznaczone i niezaznaczone .
Wszystkie wyjątki, które dziedziczą RuntimeException
lub Error
są uważane za niesprawdzone wyjątki . Wszystkie inne są zaznaczonymi wyjątkami .
Dwadzieścia lat po wprowadzeniu sprawdzanych wyjątków prawie każdy programista Javy uważa to za błąd. W popularnych współczesnych frameworkach 95% wszystkich wyjątków nie jest zaznaczonych. Język C#, który prawie dokładnie skopiował Javę, nie dodał sprawdzonych wyjątków .
Jaka jest główna różnica między sprawdzonymi i niezaznaczonymi wyjątkami?
Na sprawdzone wyjątki nakładane są dodatkowe wymagania . Z grubsza mówiąc są to:
Wymóg 1
Jeśli metoda zgłasza sprawdzony wyjątek , musi wskazać typ wyjątku w swojej sygnaturze . W ten sposób każda metoda, która ją wywołuje, jest świadoma, że może w niej wystąpić ten „znaczący wyjątek”.
Wskaż sprawdzone wyjątki po parametrach metody po słowie throws
kluczowym (nie używaj słowa throw
kluczowego przez pomyłkę). Wygląda to mniej więcej tak:
type method (parameters) throws exception
Przykład:
sprawdzony wyjątek | niesprawdzony wyjątek |
---|---|
|
|
W przykładzie po prawej stronie nasz kod zgłasza niesprawdzony wyjątek — nie jest wymagane żadne dodatkowe działanie. W przykładzie po lewej stronie metoda zgłasza sprawdzony wyjątek, więc throws
słowo kluczowe jest dodawane do sygnatury metody wraz z typem wyjątku.
Jeśli metoda spodziewa się zgłosić wiele sprawdzonych wyjątków, wszystkie z nich muszą być określone po throws
słowie kluczowym, oddzielone przecinkami. Kolejność nie jest ważna. Przykład:
public void calculate(int n) throws Exception, IOException
{
if (n == 0)
throw new Exception("n is null!");
if (n == 1)
throw new IOException("n is 1");
}
Wymóg 2
Jeśli wywołasz metodę, która ma sprawdzone wyjątki w swojej sygnaturze, nie możesz zignorować faktu, że je zgłasza.
Musisz albo przechwycić wszystkie takie wyjątki, dodając catch
bloki dla każdego z nich, albo dodając je do throws
klauzuli dla swojej metody.
To tak, jakbyśmy mówili: „ Te wyjątki są tak ważne, że musimy je wychwycić. A jeśli nie wiemy, jak sobie z nimi poradzić, to każdy, kto mógłby wywołać naszą metodę, musi zostać powiadomiony, że takie wyjątki mogą w niej wystąpić.
Przykład:
Wyobraź sobie, że piszemy metodę tworzenia świata zamieszkałego przez ludzi. Początkowa liczba osób jest przekazywana jako argument. Musimy więc dodać wyjątki, jeśli jest zbyt mało osób.
Tworzenie Ziemi | Notatka |
---|---|
|
Metoda potencjalnie zgłasza dwa sprawdzone wyjątki:
|
To wywołanie metody można obsłużyć na 3 sposoby:
1. Nie wyłapuj żadnych wyjątków
Najczęściej robi się to wtedy, gdy metoda nie wie, jak właściwie poradzić sobie z sytuacją.
Kod | Notatka |
---|---|
|
Metoda wywołująca nie wyłapuje wyjątków i musi informować o nich innych: dodaje je do własnej throws klauzuli |
2. Złap kilka wyjątków
Poradzimy sobie z błędami, z którymi możemy sobie poradzić. Ale te, których nie rozumiemy, rzucamy je do metody wywołującej. Aby to zrobić, musimy dodać ich nazwę do klauzuli throws:
Kod | Notatka |
---|---|
|
Obiekt wywołujący przechwytuje tylko jeden sprawdzony wyjątek — LonelyWorldException . Drugi wyjątek należy dodać do jego sygnatury, wskazując go po słowie throws kluczowym |
3. Złap wszystkie wyjątki
Jeśli metoda nie zgłasza wyjątków do metody wywołującej, metoda wywołująca jest zawsze pewna, że wszystko działa dobrze. I nie będzie w stanie podjąć żadnych działań w celu naprawienia wyjątkowych sytuacji.
Kod | Notatka |
---|---|
|
Wszystkie wyjątki są przechwytywane w tej metodzie. Rozmówca będzie miał pewność, że wszystko poszło dobrze. |
3. Zawijanie wyjątków
Sprawdzone wyjątki w teorii wydawały się fajne, ale w praktyce okazały się ogromną frustracją.
Załóżmy, że masz w swoim projekcie bardzo popularną metodę. Jest wywoływany z setek miejsc w twoim programie. I decydujesz się dodać do niego nowy sprawdzony wyjątek. I może się zdarzyć, że ten sprawdzony wyjątek jest naprawdę ważny i tak wyjątkowy, że tylko main()
metoda wie, co zrobić, jeśli zostanie przechwycona.
Oznacza to , że będziesz musiał dodać sprawdzony wyjątek do throws
klauzuli każdej metody, która wywołuje twoją bardzo popularną metodę . Jak również w throws
klauzuli wszystkich metod, które wywołują te metody. I metod, które wywołują te metody.
W rezultacie throws
klauzule połowy metod w projekcie otrzymują nowy sprawdzony wyjątek. I oczywiście twój projekt jest objęty testami, a teraz testy się nie kompilują. A teraz musisz także edytować klauzule throws w swoich testach.
A potem cały twój kod (wszystkie zmiany w setkach plików) będzie musiał zostać przejrzany przez innych programistów. I w tym momencie zadajemy sobie pytanie, dlaczego dokonaliśmy tak wielu krwawych zmian w projekcie? Dni pracy i zepsute testy — wszystko po to, by dodać jeden sprawdzony wyjątek?
I oczywiście nadal istnieją problemy związane z dziedziczeniem i nadpisywaniem metod. Problemy wynikające ze sprawdzonych wyjątków są znacznie większe niż korzyści. Najważniejsze jest to, że teraz niewielu ludzi je kocha i niewielu z nich korzysta.
Jednak nadal istnieje wiele kodu (w tym standardowy kod biblioteki Java), który zawiera te sprawdzone wyjątki. Co z nimi zrobić? Nie możemy ich ignorować i nie wiemy, jak sobie z nimi radzić.
Programiści Javy zaproponowali zawijanie sprawdzonych wyjątków w RuntimeException
. Innymi słowy, przechwyć wszystkie zaznaczone wyjątki, a następnie utwórz niezaznaczone wyjątki (na przykład RuntimeException
) i zamiast tego je wyrzuć. Robienie tego wygląda mniej więcej tak:
try
{
// Code where a checked exception might occur
}
catch(Exception exp)
{
throw new RuntimeException(exp);
}
Nie jest to zbyt ładne rozwiązanie, ale nie ma tu nic kryminalnego: wyjątek został po prostu wepchnięty do pliku RuntimeException
.
W razie potrzeby możesz łatwo go stamtąd odzyskać. Przykład:
Kod | Notatka |
---|---|
|
Uzyskaj wyjątek przechowywany w RuntimeException obiekcie. Zmienna cause może null określić jej typ i przekonwertować ją na sprawdzony typ wyjątku. |
4. Łapanie wielu wyjątków
Programiści naprawdę nienawidzą powielać kodu. Wymyślili nawet odpowiednią zasadę rozwoju: Nie powtarzaj się (DRY) . Ale podczas obsługi wyjątków często zdarza się, że po try
bloku następuje kilka catch
bloków z tym samym kodem.
Lub mogą istnieć 3 catch
bloki z tym samym kodem i kolejne 2 catch
bloki z innym identycznym kodem. Jest to standardowa sytuacja, gdy Twój projekt odpowiedzialnie obsługuje wyjątki.
Począwszy od wersji 7, w języku Java dodano możliwość określenia wielu typów wyjątków w jednym catch
bloku. Wygląda to mniej więcej tak:
try
{
// Code where an exception might occur
}
catch (ExceptionType1 | ExceptionType2 | ExceptionType3 name)
{
// Exception handling code
}
Możesz mieć tyle catch
bloków, ile chcesz. Jednak pojedynczy catch
blok nie może określać wyjątków, które dziedziczą się nawzajem. Innymi słowy, nie możesz napisać catch ( Exception
| RuntimeException
e), ponieważ RuntimeException
klasa dziedziczy Exception
.
5. Niestandardowe wyjątki
Zawsze możesz utworzyć własną klasę wyjątków. Po prostu tworzysz klasę, która dziedziczy RuntimeException
klasę. Będzie wyglądać mniej więcej tak:
class ClassName extends RuntimeException
{
}
Omówimy szczegóły, gdy nauczysz się OOP, dziedziczenia, konstruktorów i nadpisywania metod.
Jednak nawet jeśli masz tylko prostą klasę taką jak ta (całkowicie bez kodu), nadal możesz na jej podstawie zgłaszać wyjątki:
Kod | Notatka |
---|---|
|
Rzuć niezaznaczony MyException . |
W zadaniu Java Multithreading zagłębimy się w pracę z naszymi własnymi niestandardowymi wyjątkami.
GO TO FULL VERSION