JavaRush /Blog Java /Random-PL /Wyjątki w Javie: łapanie i obsługa

Wyjątki w Javie: łapanie i obsługa

Opublikowano w grupie Random-PL
Cześć! Nie chcę ci tego mówić, ale ogromna część pracy programisty polega na radzeniu sobie z błędami. I najczęściej - z własnym. Tak się składa, że ​​nie ma ludzi, którzy nie popełniają błędów. I takich programów też nie ma. Oczywiście najważniejszą rzeczą podczas pracy nad błędem jest zrozumienie jego przyczyny. Powodów takiego stanu rzeczy w programie może być całe mnóstwo. W pewnym momencie twórcy Javy stanęli przed pytaniem: co zrobić z tak potencjalnymi błędami w programach? Całkowite ich unikanie jest nierealne. Programiści potrafią napisać coś, czego nawet nie można sobie wyobrazić :) Oznacza to, że konieczne jest wbudowanie w język mechanizmu radzenia sobie z błędami. Innymi słowy, jeśli w programie pojawił się jakiś błąd, do dalszej pracy potrzebny jest skrypt. Co dokładnie powinien zrobić program, gdy wystąpi błąd? Dziś zapoznamy się z tym mechanizmem. I nazywa się to „Wyjątkami .

Jaki jest wyjątek w Javie

Wyjątek stanowi wyjątkowa, niezaplanowana sytuacja, która wydarzyła się w trakcie funkcjonowania programu. Przykładów wyjątków w Javie może być wiele. Na przykład napisałeś kod, który odczytuje tekst z pliku i wyświetla pierwszą linię w konsoli.
public class Main {

   public static void main(String[] args) throws IOException {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
       String firstString = reader.readLine();
       System.out.println(firstString);
   }
}
Ale taki plik nie istnieje! Wynik programu będzie wyjątkiem - FileNotFoundException. Wniosek:

Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
Każdy wyjątek jest reprezentowany przez oddzielną klasę w Javie. Wszystkie klasy wyjątków pochodzą od wspólnego „przodka” – klasy nadrzędnej Throwable. Nazwa klasy wyjątku zwykle krótko odzwierciedla przyczynę jej wystąpienia:
  • FileNotFoundException(nie znaleziono pliku)
  • ArithmeticException(wyjątek podczas wykonywania operacji matematycznej)
  • ArrayIndexOutOfBoundsException(numer komórki tablicy jest podany poza jej długością). Na przykład, jeśli spróbujesz wyświetlić tablicę komórek [23] na konsoli dla tablicy tablicowej o długości 10.
W Javie jest prawie 400 takich klas! Dlaczego tak dużo? Właśnie po to, aby ułatwić programistom pracę z nimi. Wyobraź sobie: napisałeś program, a kiedy zostanie uruchomiony, zgłasza wyjątek wyglądający tak:
Exception in thread "main"
Uh-uh :/ Nic nie jest jasne. Nie jest jasne, jakiego rodzaju jest to błąd i skąd się wziął. Nie ma żadnych przydatnych informacji. Ale dzięki tak różnorodności klas programista otrzymuje dla siebie najważniejsze - rodzaj błędu i jego prawdopodobną przyczynę, która zawarta jest w nazwie klasy. Przecież to zupełnie inna rzecz, którą można zobaczyć w konsoli:
Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
Natychmiast staje się jasne, na czym może polegać problem i „w którą stronę kopać”, aby go rozwiązać! Wyjątkami, jak wszystkie instancje klas, są obiekty.

Łapanie i obsługa wyjątków

Aby pracować z wyjątkami w Javie, istnieją specjalne bloki kodu try: catchi finally. Wyjątki: przechwytywanie i przetwarzanie - 2Kod, w którym programista spodziewa się wystąpienia wyjątków, jest umieszczany w bloku try. Nie oznacza to, że w tym miejscu koniecznie wystąpi wyjątek. Oznacza to, że tam może się to zdarzyć i programista jest tego świadomy. Typ błędu, jakiego oczekujesz, jest umieszczony w bloku catch(„złapanie”). W tym miejscu umieszczany jest także cały kod, który należy wykonać w przypadku wystąpienia wyjątku. Oto przykład:
public static void main(String[] args) throws IOException {
   try {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));

       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {

       System.out.println(„Błąd! Nie znaleziono pliku!”);
   }
}
Wniosek:

Ошибка! Файл не найден!
Nasz kod umieściliśmy w dwóch blokach. W pierwszym bloku spodziewamy się wystąpienia błędu „Nie znaleziono pliku”. To jest blok try. W drugim mówimy programowi, co ma zrobić, jeśli wystąpi błąd. Ponadto istnieje specyficzny rodzaj błędu - FileNotFoundException. Jeśli w nawiasach blokowych przekażemy catchinną klasę wyjątku, nie zostanie ona przechwycona.
public static void main(String[] args) throws IOException {
   try {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (ArithmeticException e) {

       System.out.println(„Błąd! Nie znaleziono pliku!”);
   }
}
Wniosek:

Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
Kod w bloku catchnie zadziałał, ponieważ „skonfigurowaliśmy” ten blok do przechwytywania ArithmeticException, a kod w bloku trywyrzucił inny typ - FileNotFoundException. Nie pisaliśmy skryptu dla FileNotFoundException, więc program wyświetlił w konsoli informacje, które są domyślnie wyświetlane dla FileNotFoundException. Tutaj należy zwrócić uwagę na 3 rzeczy. Pierwszy. Gdy tylko w którymkolwiek wierszu kodu w bloku try wystąpi wyjątek, kod następujący po nim nie będzie już wykonywany. Wykonanie programu natychmiast „przeskoczy” do bloku catch. Na przykład:
public static void main(String[] args) {
   try {
       System.out.println(„Podziel liczbę przez zero”);
       System.out.println(366/0);//ta linia kodu zgłosi wyjątek

       System.out.println("Ten");
       System.out.println("kod");
       System.out.println("Nie");
       System.out.println("będzie");
       System.out.println("zrobione!");

   } catch (ArithmeticException e) {

       System.out.println(„Program przeskoczył do bloku catch!”);
       System.out.println(„Błąd! Nie można dzielić przez zero!”);
   }
}
Wniosek:

Делим число на ноль 
Программа перепрыгнула в блок catch! 
Ошибка! Нельзя делить на ноль! 
W bloku tryw drugiej linii próbowaliśmy podzielić liczbę przez 0, co spowodowało wyjątek ArithmeticException. Następnie linie 6-10 bloku trynie będą już wykonywane. Jak powiedzieliśmy, program natychmiast rozpoczął wykonywanie bloku catch. Drugi. Może być kilka bloków catch. Jeśli kod w bloku trymoże zgłosić nie jeden, ale kilka typów wyjątków, możesz napisać własny blok dla każdego z nich catch.
public static void main(String[] args) throws IOException {
   try {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));

       System.out.println(366/0);
       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {

       System.out.println(„Błąd! Nie znaleziono pliku!”);

   } catch (ArithmeticException e) {

       System.out.println(„Błąd! Dzielenie przez 0!”);

   }
}
W tym przykładzie napisaliśmy dwa bloki catch. Jeżeli w bloku trywystąpi FileNotFoundException, zostanie wykonany pierwszy blok catch. Jeśli tak się stanie ArithmeticException, zostanie wykonany drugi. Możesz napisać co najmniej 50 bloków catch, ale oczywiście lepiej nie pisać kodu, który może wyrzucić 50 różnych typów błędów :) Po trzecie. Skąd wiesz, jakie wyjątki może zgłosić Twój kod? No cóż, można się oczywiście domyślać, ale nie da się wszystkiego mieć w głowie. Dlatego kompilator Java zna najczęstsze wyjątki i wie, w jakich sytuacjach mogą wystąpić. Na przykład, jeśli napisałeś kod, a kompilator wie, że podczas jego działania mogą wystąpić 2 rodzaje wyjątków, Twój kod nie zostanie skompilowany, dopóki ich nie obsłużysz. Poniżej zobaczymy tego przykłady. Teraz odnośnie obsługi wyjątków. Istnieją 2 sposoby ich przetwarzania. Pierwszy z nich poznaliśmy już - metoda może obsłużyć wyjątek samodzielnie w bloku catch(). Istnieje druga opcja — metoda może zgłosić wyjątek w górę stosu wywołań. Co to znaczy? Przykładowo w naszej klasie mamy metodę - tę samą printFirstString()- która czyta plik i wyświetla jego pierwszą linijkę w konsoli:
public static void printFirstString(String filePath) {

   BufferedReader reader = new BufferedReader(new FileReader(filePath));
   String firstString = reader.readLine();
   System.out.println(firstString);
}
Obecnie nasz kod nie kompiluje się, ponieważ zawiera nieobsługiwane wyjątki. W linii 1 podajesz ścieżkę do pliku. Kompilator wie, że taki kod może łatwo doprowadzić do FileNotFoundException. W linii 3 czytasz tekst z pliku. W tym procesie IOExceptionłatwo może wystąpić błąd podczas przetwarzania danych wejścia-wyjścia (Input-Output). Teraz kompilator mówi ci: „Stary, nie zatwierdzę tego kodu ani go nie skompiluję, dopóki nie powiesz mi, co powinienem zrobić, jeśli wystąpi jeden z tych wyjątków. I na pewno mogą się zdarzyć w oparciu o kod, który napisałeś!” . Nie ma dokąd pójść, musisz przetworzyć jedno i drugie! Pierwsza opcja przetwarzania jest nam już znana: musimy umieścić nasz kod w bloku tryi dodać dwa bloki catch:
public static void printFirstString(String filePath) {

   try {
       BufferedReader reader = new BufferedReader(new FileReader(filePath));
       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {
       System.out.println(„Błąd, nie znaleziono pliku!”);
       e.printStackTrace();
   } catch (IOException e) {
       System.out.println(„Błąd podczas wprowadzania/wyprowadzania danych z pliku!”);
       e.printStackTrace();
   }
}
Ale to nie jedyna opcja. Możemy uniknąć pisania skryptu dla błędu wewnątrz metody i po prostu rzucić wyjątek na górę. Odbywa się to za pomocą słowa kluczowego throws, które jest zapisane w deklaracji metody:
public static void printFirstString(String filePath) throws FileNotFoundException, IOException {
   BufferedReader reader = new BufferedReader(new FileReader(filePath));
   String firstString = reader.readLine();
   System.out.println(firstString);
}
Po słowie throwswyszczególniamy, oddzielone przecinkami, wszystkie typy wyjątków, jakie ta metoda może zgłosić podczas działania. Dlaczego to się robi? Teraz, jeśli ktoś w programie będzie chciał wywołać metodę printFirstString(), będzie musiał sam zaimplementować obsługę wyjątków. Na przykład w innej części programu jeden z Twoich kolegów napisał metodę, w ramach której wywołuje Twoją metodę printFirstString():
public static void yourColleagueMethod() {

   //...metoda twojego kolegi coś robi

   //...i w pewnym momencie wywołuje twoją metodę printFirstString() z plikiem, którego potrzebuje
   printFirstString("C:\\Users\\Eugene\\Desktop\\testFile.txt");
}
Błąd, kod się nie kompiluje! W metodzie printFirstString()nie napisaliśmy skryptu obsługującego błędy . Dlatego zadanie spada na barki tych, którzy będą korzystać z tej metody. Oznacza to, że metoda yourColleagueMethod()ma teraz te same 2 opcje: musi albo przetworzyć oba wyjątki, które „przeleciały” do niej za pomocą try-catch, albo przekazać je dalej.
public static void yourColleagueMethod() throws FileNotFoundException, IOException {
   //... metoda coś robi

   //...i w pewnym momencie wywołuje twoją metodę printFirstString() z plikiem, którego potrzebuje
   printFirstString("C:\\Users\\Eugene\\Desktop\\testFile.txt");
}
W drugim przypadku przetwarzanie spadnie na barki kolejnej metody na stosie – tej, która wywoła yourColleagueMethod(). Dlatego taki mechanizm nazywa się „rzucaniem wyjątku w górę” lub „przechodzeniem na górę”. Kiedy zgłaszasz wyjątki za pomocą throws, kod się kompiluje. W tym momencie kompilator zdaje się mówić: „OK, OK. Twój kod zawiera wiele potencjalnych wyjątków, ale i tak go skompiluję. Wrócimy do tej rozmowy!” A kiedy gdzieś w programie wywołasz metodę, która nie obsłużyła jej wyjątków, kompilator spełnia swoją obietnicę i ponownie o nich przypomina. Na koniec porozmawiamy o bloku finally(przepraszam za grę słów). To ostatnia część triumwiratu obsługującego wyjątki try-catch-finally. Jego osobliwością jest to, że jest wykonywany w dowolnym scenariuszu działania programu.
public static void main(String[] args) throws IOException {
   try {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));

       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {
       System.out.println(„Błąd! Nie znaleziono pliku!”);
       e.printStackTrace();
   } finally {
       System.out.println(„A oto ostatni blok!”);
   }
}
W tym przykładzie kod znajdujący się w bloku finallyjest wykonywany w obu przypadkach. Jeśli kod w bloku tryzostanie wykonany w całości i nie zgłosi wyjątku, blok zostanie uruchomiony na końcu finally. Jeżeli znajdujący się w środku kod tryzostanie przerwany i program przeskoczy do bloku catch, po wykonaniu zawartego w nim kodu catch, blok nadal będzie wybrany finally. Dlaczego jest to potrzebne? Jego głównym celem jest wykonanie wymaganej części kodu; część, którą należy wypełnić niezależnie od okoliczności. Na przykład często zwalnia część zasobów używanych przez program. W naszym kodzie otwieramy strumień, aby odczytać informacje z pliku i przekazać je do pliku BufferedReader. readerTrzeba zamknąć naszą i uwolnić zasoby. Należy to zrobić w każdym przypadku: nie ma znaczenia, czy program działa zgodnie z oczekiwaniami, czy zgłasza wyjątek. Wygodnie jest to zrobić w bloku finally:
public static void main(String[] args) throws IOException {

   BufferedReader reader = null;
   try {
       reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));

       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {
       e.printStackTrace();
   } finally {
       System.out.println(„A oto ostatni blok!”);
       if (reader != null) {
           reader.close();
       }
   }
}
Teraz mamy całkowitą pewność, że zadbaliśmy o zajęte zasoby, niezależnie od tego, co stanie się w trakcie działania programu :) To nie wszystko, co musisz wiedzieć o wyjątkach. Obsługa błędów to bardzo ważny temat w programowaniu: poświęcono mu więcej niż jeden artykuł. Na następnej lekcji dowiemy się jakie są rodzaje wyjątków i jak stworzyć własny wyjątek :) Do zobaczenia!
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION