CodeGym /Blog Java /Random-PL /Wyjątki: łapanie i obchodzenie się
Autor
Oleksandr Miadelets
Head of Developers Team at CodeGym

Wyjątki: łapanie i obchodzenie się

Opublikowano w grupie Random-PL
Cześć! Nienawidzę o tym wspominać, ale ogromna część pracy programisty to radzenie sobie z błędami. Najczęściej jego własne. Okazuje się, że nie ma ludzi, którzy nie popełniają błędów. A takich programów też nie ma. Wyjątki: łapanie i obsługa - 1 Oczywiście, gdy mamy do czynienia z błędem, najważniejsze jest zrozumienie jego przyczyny. I wiele rzeczy może powodować błędy w programie. W pewnym momencie twórcy Javy zadali sobie pytanie, co zrobić z najbardziej prawdopodobnymi błędami programistycznymi? Całkowite unikanie ich nie jest realistyczne, programiści są w stanie pisać rzeczy, których nawet nie możesz sobie wyobrazić. :) Więc musimy dać językowi mechanizm pracy z błędami. Innymi słowy, jeśli w twoim programie jest błąd, potrzebujesz skryptu, który określi, co dalej. Co dokładnie powinien zrobić program, gdy wystąpi błąd? Dziś poznamy ten mechanizm. Nazywa się to „ wyjątkami w Javie ”.

Co to jest wyjątek?

Wyjątkiem jest wyjątkowa, nieplanowana sytuacja, która ma miejsce podczas działania programu. Istnieje wiele wyjątków. Na przykład napisałeś kod, który odczytuje tekst z pliku i wyświetla pierwszy wiersz.

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 co, jeśli nie ma takiego pliku! Program wygeneruje wyjątek: FileNotFoundException. Dane wyjściowe: Wyjątek w wątku „main” java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (System nie może znaleźć określonej ścieżki) W Javie każdy wyjątek jest reprezentowany przez oddzielną klasę. Wszystkie te klasy wyjątków wywodzą się od wspólnego „przodka” — Throwableklasy nadrzędnej. Nazwa klasy wyjątku zwykle zwięźle odzwierciedla przyczynę wystąpienia wyjątku:
  • FileNotFoundException(plik nie został znaleziony)

  • ArithmeticException(wystąpił wyjątek podczas wykonywania operacji matematycznej)

  • ArrayIndexOutOfBoundsException(indeks wykracza poza granice tablicy). Na przykład ten wyjątek występuje, jeśli próbujesz wyświetlić pozycję 23 tablicy, która ma tylko 10 elementów.
W sumie Java ma prawie 400 takich klas! Dlaczego tak dużo? Aby ułatwić pracę programistom. Wyobraź sobie taką sytuację: piszesz program, który podczas działania generuje wyjątek, który wygląda tak:

Exception in thread "main"
Uhhh. :/ Niewiele to pomaga. Nie jest jasne, co oznacza błąd ani skąd się wziął. Nie ma tu pomocnych informacji. Ale duża różnorodność klas wyjątków w Javie daje programiście to, co najważniejsze: typ błędu i jego prawdopodobną przyczynę (osadzone w nazwie klasy). To zupełnie inna rzecz do zobaczenia

Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (The system cannot find the specified path)
Od razu wiadomo, na czym może polegać problem i gdzie zacząć kopać, aby go rozwiązać! Wyjątki, podobnie jak instancje dowolnych klas, są obiektami.

Łapanie i obsługa wyjątków

Java ma specjalne bloki kodu do pracy z wyjątkami: try, catchi finally. Wyjątki: łapanie i obsługa - 2 Kod, w przypadku którego programista uważa, że ​​może wystąpić wyjątek, jest umieszczany w trybloku. Nie oznacza to, że wystąpi tutaj wyjątek. Oznacza to, że może tu wystąpić, a programista jest świadomy takiej możliwości. Typ błędu, którego wystąpienia oczekujesz, jest umieszczony w catchbloku. Zawiera również cały kod, który powinien zostać wykonany 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("Error! File not found!");
   }
}
Wyjście: Błąd! Nie znaleziono pliku! Umieściliśmy nasz kod w dwóch blokach. W pierwszym bloku przewidujemy, że może wystąpić błąd „Nie znaleziono pliku”. To jest tryblok. W drugim mówimy programowi, co ma zrobić, jeśli wystąpi błąd. I konkretny typ błędu: FileNotFoundException. Jeśli umieścimy inną klasę wyjątku w nawiasach bloku catch, to FileNotFoundExceptionnie zostanie złapany.

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("Error! File not found!");
   }
}
Dane wyjściowe: Wyjątek w wątku „main” java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (System nie może znaleźć określonej ścieżki) Kod w catchbloku nie został uruchomiony, ponieważ „skonfigurowaliśmy” ten blok do catch ArithmeticException, a kod w trybloku rzucił inny typ: FileNotFoundException. Nie napisaliśmy żadnego kodu do obsługi FileNotFoundException, więc program wyświetla domyślne informacje dla FileNotFoundException. Tutaj musisz zwrócić uwagę na trzy rzeczy. Numer jeden. Gdy w jakiejś linii w bloku wystąpi wyjątek try, następujący po nim kod nie zostanie wykonany. Wykonanie programu natychmiast „przeskakuje” do catchbloku. Na przykład:

public static void main(String[] args) {
   try {
       System.out.println("Divide by zero");
       System.out.println(366/0);// This line of code will throw an exception

       System.out.println("This");
       System.out.println("code");
       System.out.println("will not");
       System.out.println("be");
       System.out.println("executed!");

   } catch (ArithmeticException e) {

       System.out.println("The program jumped to the catch block!");
       System.out.println("Error! You can't divide by zero!");
   }
}
Wyjście: Dzielenie przez zero Program przeskoczył do bloku catch! Błąd! Nie można dzielić przez zero! W drugim wierszu bloku trypróbujemy podzielić przez 0, co daje ArithmeticException. W konsekwencji wiersze 3-9 bloku trynie zostaną wykonane. Jak powiedzieliśmy, program natychmiast rozpoczyna wykonywanie catchbloku. Numer dwa. Bloków może być kilka catch. Jeśli kod w trybloku może zgłosić nie jeden, ale kilka różnych typów wyjątków, możesz napisać catchblok dla każdego z nich.

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("Error! File not found!");
      
   } catch (ArithmeticException e) {

       System.out.println("Error! Division by 0!");
      
   }
}
W tym przykładzie napisaliśmy dwa catchbloki. Jeśli FileNotFoundExceptionw bloku wystąpi a, wówczas wykonany zostanie trypierwszy blok. catchJeśli ArithmeticExceptionwystąpi, zostanie wykonany drugi blok. Możesz napisać 50 catchbloków, jeśli chcesz. Oczywiście lepiej nie pisać kodu, który mógłby rzucić 50 różnych rodzajów wyjątków. :) Trzeci. Skąd wiesz, jakie wyjątki może rzucić Twój kod? Cóż, możesz być w stanie odgadnąć niektóre z nich, ale nie możesz trzymać wszystkiego w głowie. Dlatego kompilator Javy zna najczęstsze wyjątki i sytuacje, w których mogą się one pojawić. Na przykład, jeśli napiszesz kod, o którym kompilator wie, że może zgłaszać dwa typy wyjątków, Twój kod nie skompiluje się, dopóki ich nie obsłużysz. Przykłady tego zobaczymy poniżej. Teraz kilka słów o obsłudze wyjątków. Istnieją 2 sposoby obsługi wyjątków. Pierwszy już napotkaliśmy: metoda może obsłużyć sam wyjątek w catch()bloku. Istnieje druga opcja: metoda może ponownie zgłosić wyjątek w górę stosu wywołań. Co to znaczy? Na przykład mamy klasę z tą samą printFirstString()metodą, która czyta plik i wyświetla jego pierwszą linię:

public static void printFirstString(String filePath) {

   BufferedReader reader = new BufferedReader(new FileReader(filePath));
   String firstString = reader.readLine();
   System.out.println(firstString);
}
Obecnie nasz kod się nie kompiluje, ponieważ zawiera nieobsłużone wyjątki. W linii 1 określasz ścieżkę do pliku. Kompilator wie, że taki kod mógłby z łatwością wygenerować plik FileNotFoundException. W linii 3 czytasz tekst z pliku. Ten proces może łatwo spowodować IOException(błąd wejścia/wyjścia). Teraz kompilator mówi do ciebie: „Stary, nie zatwierdzę tego kodu i nie skompiluję go, dopóki nie powiesz mi, co powinienem zrobić, jeśli wystąpi jeden z tych wyjątków. I z pewnością mogą się zdarzyć na podstawie kodu, który napisałeś !" Nie da się tego obejść: musisz poradzić sobie z obydwoma! Znamy już pierwszą metodę obsługi wyjątków: musimy umieścić nasz kod w bloku tryi dodać dwa catchbloki:

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("Error, file not found!");
       e.printStackTrace();
   } catch (IOException e) {
       System.out.println("File input/output error!");
       e.printStackTrace();
   }
}
Ale to nie jedyna opcja. Moglibyśmy po prostu rzucić wyjątek wyżej, zamiast pisać kod obsługujący błędy wewnątrz metody. Odbywa się to za pomocą słowa kluczowego throwsw 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 kluczowym throwswskazujemy oddzieloną przecinkami listę wszystkich typów wyjątków, które metoda może zgłosić. Dlaczego? Teraz, jeśli ktoś chce wywołać printFirstString()metodę w programie, to on (nie ty) będzie musiał zaimplementować obsługę wyjątków. Załóżmy na przykład, że w innym miejscu programu jeden z twoich kolegów napisał metodę, która wywołuje twoją printFirstString()metodę:

public static void yourColleagueMethod() {

   // Your colleague's method does something

   //...and then calls your printFirstString() method with the file it needs
   printFirstString("C:\\Users\\Henry\\Desktop\\testFile.txt");
}
Otrzymujemy błąd! Ten kod się nie skompiluje! Nie napisaliśmy kodu obsługi wyjątków w printFirstString()metodzie. W rezultacie zadanie to spada teraz na barki tych, którzy stosują tę metodę. Innymi słowy, methodWrittenByYourColleague()metoda ma teraz te same 2 opcje: musi albo użyć try-catchbloku do obsługi obu wyjątków, albo ponownie je zgłosić.

public static void yourColleagueMethod() throws FileNotFoundException, IOException {
   // The method does something

   //...and then calls your printFirstString() method with the file it needs
   printFirstString("C:\\Users\\Henry\\Desktop\\testFile.txt");
}
W drugim przypadku następna metoda w stosie wywołań — ta wywołująca methodWrittenByYourColleague()— będzie musiała obsłużyć wyjątki. Dlatego nazywamy to „rzucaniem lub przekazywaniem wyjątku”. Jeśli rzucisz wyjątki w górę za pomocą słowa kluczowego throws, Twój kod się skompiluje. W tym momencie kompilator wydaje się mówić: „Dobrze, dobrze. Twój kod zawiera kilka potencjalnych wyjątków, ale skompiluję go. Ale wrócimy do tej rozmowy!” A kiedy wywołasz jakąkolwiek metodę, która ma nieobsługiwane wyjątki, kompilator spełni swoją obietnicę i ponownie o nich przypomni. Na koniec porozmawiamy o finallybloku (przepraszam za kalambur). To ostatnia część try-catch-finallytriumwiratu obsługi wyjątków..

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("Error! File not found!");
       e.printStackTrace();
   } finally {
       System.out.println ("And here's the finally block!");
   }
}
W tym przykładzie kod wewnątrz finallybloku zostanie wykonany w obu przypadkach. Jeśli kod w trybloku zostanie uruchomiony w całości bez zgłaszania wyjątków, finallyblok zostanie uruchomiony na końcu. Jeśli kod wewnątrz trybloku zostanie przerwany przez wyjątek i program przeskoczy do catchbloku, finallyblok będzie nadal wykonywany po kodzie wewnątrz catchbloku. Dlaczego jest to konieczne? Jego głównym celem jest wykonanie kodu obowiązkowego: kodu, który należy wykonać niezależnie od okoliczności. Na przykład często zwalnia niektóre zasoby używane przez program. W naszym kodzie otwieramy strumień, aby odczytać informacje z pliku i przekazać je do BufferedReaderobiektu. Musimy zamknąć naszego czytnika i zwolnić zasoby. Należy to zrobić bez względu na wszystko — kiedy program działa tak, jak powinien, i kiedy zgłasza wyjątek. Blok finallyjest do tego bardzo dogodnym miejscem:

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 ("And here's the finally block!");
       if (reader != null) {
           reader.close();
       }
   }
}
Teraz mamy pewność, że zadbamy o zasoby, niezależnie od tego, co się stanie podczas 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 wiele artykułów. W następnej lekcji dowiemy się, jakie są rodzaje wyjątków i jak tworzyć własne wyjątki. :) Do zobaczenia!
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION