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.
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 kodutry
: catch
i finally
. Kod, 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 catch
inną 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 catch
nie zadziałał, ponieważ „skonfigurowaliśmy” ten blok do przechwytywania ArithmeticException
, a kod w bloku try
wyrzucił 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 try
w drugiej linii próbowaliśmy podzielić liczbę przez 0, co spowodowało wyjątek ArithmeticException
. Następnie linie 6-10 bloku try
nie 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 try
moż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 try
wystą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 try
i 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 throws
wyszczegó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 finally
jest wykonywany w obu przypadkach. Jeśli kod w bloku try
zostanie 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 try
zostanie 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
. reader
Trzeba 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!
GO TO FULL VERSION