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.
Throwableklasa
Klasą bazową dla wszystkich wyjątków jest Throwableklasa. Klasa Throwablezawiera 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 Throwableklasy. I chociaż teoretycznie możesz pisać kod taki jak throw new Throwable();, nikt zwykle tego nie robi. Głównym celem klasy Throwablejest posiadanie jednej klasy nadrzędnej dla wszystkich wyjątków.
Errorklasa
Następną klasą wyjątków jest Errorklasa, która bezpośrednio dziedziczy Throwableklasę. 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.
Exceptionklasa
Klasy Exceptioni RuntimeExceptiondotyczą 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 catchblok, 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.
RuntimeExceptionklasa
RuntimeExceptionssą podzbiorem Exceptions. Można nawet powiedzieć, że RuntimeExceptionjest 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 Exceptiona RuntimeExceptionpóźniej.
2. Throws: sprawdzone wyjątki

Wszystkie wyjątki Java dzielą się na 2 kategorie: zaznaczone i niezaznaczone .
Wszystkie wyjątki, które dziedziczą RuntimeExceptionlub Errorsą 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 throwskluczowym (nie używaj słowa throwkluczowego 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 throwssł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 throwssł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 catchbloki dla każdego z nich, albo dodając je do throwsklauzuli 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 throwsklauzuli |
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 throwskluczowym |
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 throwsklauzuli każdej metody, która wywołuje twoją bardzo popularną metodę . Jak również w throwsklauzuli wszystkich metod, które wywołują te metody. I metod, które wywołują te metody.
W rezultacie throwsklauzule 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 RuntimeExceptionobiekcie. Zmienna causemoże nullokreś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 trybloku następuje kilka catchbloków z tym samym kodem.
Lub mogą istnieć 3 catchbloki z tym samym kodem i kolejne 2 catchbloki 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 catchbloku. 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 catchbloków, ile chcesz. Jednak pojedynczy catchblok nie może określać wyjątków, które dziedziczą się nawzajem. Innymi słowy, nie możesz napisać catch ( Exception| RuntimeExceptione), ponieważ RuntimeExceptionklasa dziedziczy Exception.
5. Niestandardowe wyjątki
Zawsze możesz utworzyć własną klasę wyjątków. Po prostu tworzysz klasę, która dziedziczy RuntimeExceptionklasę. 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