1. Rodzaje wyjątków

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

Zgłoszenia: 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 .

Ważny!

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
public void calculate(int n) throws Exception
{
   if (n == 0)
      throw new Exception("n is null!");
}
public void calculate(n)
{
   if (n == 0)
      throw new RuntimeException("n is null!");
}

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
public void createWorld(int n) throws EmptyWorldException, LonelyWorldException
{
   if (n == 0)
      throw new EmptyWorldException("There are no people!");
   if (n == 1)
      throw new LonelyWorldException ("There aren't enough people!");
   System.out.println("A wonderful world was created. Population: " + n);
}
Metoda potencjalnie zgłasza dwa sprawdzone wyjątki:

  • Wyjątek pustego świata
  • Wyjątek LonelyWorld

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
public void createPopulatedWorld(int population)
throws EmptyWorldException, LonelyWorldException
{
   createWorld(population);
}
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
public void createNonEmptyWorld(int population)
throws EmptyWorldException
{
   try
   {
      createWorld(population);
   }
   catch (LonelyWorldException e)
   {
      e.printStackTrace();
   }
}
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
public void createAnyWorld(int population)
{
   try
   {
      createWorld(population);
   }
   catch (LonelyWorldException e)
   {
      e.printStackTrace();
   }
   catch (EmptyWorldException e)
   {
      e.printStackTrace();
   }
}
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
try
{
   // Code where we wrap the checked exception
   // in a RuntimeException
}
catch(RuntimeException e)
{
   Throwable cause = e.getCause();
   if (cause instanceof Exception)
   {
      Exception exp = (Exception) cause;
      // Exception handling code goes here
   }
}







Uzyskaj wyjątek przechowywany w RuntimeExceptionobiekcie. Zmienna causemoż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 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
class Solution
{
   public static void main(String[] args)
   {
      throw new MyException();
   }
}

class MyException extends RuntimeException
{
}




Rzuć niezaznaczony MyException .

W zadaniu Java Multithreading zagłębimy się w pracę z naszymi własnymi niestandardowymi wyjątkami.