Java Language
Wyjątki i obsługa wyjątków
Szukaj…
Wprowadzenie
Throwable
i jego podtypy mogą być wysyłane do stosu za pomocą słowa kluczowego throw
i przechwytywane za pomocą instrukcji try…catch
.
Składnia
void someMethod () zgłasza deklarację metody SomeException {} //, wymusza wychwytywanie metod, jeśli SomeException jest sprawdzonym typem wyjątku
próbować {
someMethod(); //code that might throw an exception
}
catch (SomeException e) {
System.out.println("SomeException was thrown!"); //code that will run if certain exception (SomeException) is thrown
}
Wreszcie {
//code that will always run, whether try block finishes or not
}
Łapanie wyjątku za pomocą try-catch
Można wychwycić wyjątek i obsłużyć go za pomocą instrukcji try...catch
. (W rzeczywistości instrukcje try
mają inne formy, jak opisano w innych przykładach dotyczących try...catch...finally
i try-with-resources
.)
Try-catch z jednym blokiem catch
Najprostsza forma wygląda następująco:
try {
doSomething();
} catch (SomeException e) {
handle(e);
}
// next statement
Zachowanie prostego try...catch
jest następujące:
- Instrukcje w bloku
try
są wykonywane. - Jeśli instrukcje w bloku
try
nie zgłaszają wyjątku, wówczas kontrola przechodzi do następnej instrukcji potry...catch
. - Jeśli w bloku
try
zostanie zgłoszony wyjątek.- Obiekt wyjątku jest testowany w celu sprawdzenia, czy jest to instancja
SomeException
lub podtyp. - Jeśli tak jest, to
catch
bloku będzie połowu wyjątek:- Zmienna
e
jest powiązana z obiektem wyjątku. - Kod w bloku
catch
jest wykonywany. - Jeśli ten kod zgłasza wyjątek, nowo zgłoszony wyjątek jest propagowany w miejsce oryginalnego.
- W przeciwnym razie kontrola przechodzi do następnej instrukcji po
try...catch
.
- Zmienna
- Jeśli tak nie jest, oryginalny wyjątek jest nadal propagowany.
- Obiekt wyjątku jest testowany w celu sprawdzenia, czy jest to instancja
Try-catch z wieloma połowami
try...catch
może również zawierać wiele bloków catch
. Na przykład:
try {
doSomething();
} catch (SomeException e) {
handleOneWay(e)
} catch (SomeOtherException e) {
handleAnotherWay(e);
}
// next statement
Jeśli istnieje wiele bloków catch
, są one wypróbowywane pojedynczo, zaczynając od pierwszego, aż do znalezienia dopasowania dla wyjątku. Odpowiednia procedura obsługi jest wykonywana (jak wyżej), a następnie kontrola jest przekazywana do następnej instrukcji po instrukcji try...catch
. Bloki catch
po tym, który pasuje, są zawsze pomijane, nawet jeśli kod procedury obsługi zgłasza wyjątek .
Strategia dopasowywania „z góry na dół” ma konsekwencje dla przypadków, w których wyjątki w blokach catch
nie są rozłączne. Na przykład:
try {
throw new RuntimeException("test");
} catch (Exception e) {
System.out.println("Exception");
} catch (RuntimeException e) {
System.out.println("RuntimeException");
}
Ten fragment kodu wyświetli „Wyjątek”, a nie „RuntimeException”. Ponieważ RuntimeException
jest podtypem Exception
, zostanie dopasowany pierwszy (bardziej ogólny) catch
. Drugi (bardziej szczegółowy) catch
nigdy nie zostanie wykonany.
Lekcja, której można się z tego nauczyć, polega na tym, że najbardziej specyficzne bloki catch
(pod względem typów wyjątków) powinny pojawić się pierwsze, a najbardziej ogólne powinny być ostatnie. (Niektóre kompilatory Java ostrzegają, jeśli nie można nigdy wykonać catch
, ale nie jest to błąd kompilacji).
Bloki catch dla wielu wyjątków
Począwszy od Java SE 7, pojedynczy blok catch
może obsłużyć listę niepowiązanych wyjątków. Typy wyjątków są wymienione, oddzielone symbolem pionowej kreski ( |
). Na przykład:
try {
doSomething();
} catch (SomeException | SomeOtherException e) {
handleSomeException(e);
}
Zachowanie przechwytywania wielu wyjątków jest prostym rozszerzeniem dla przypadku pojedynczego wyjątku. catch
pasuje, jeśli zgłoszony wyjątek odpowiada (przynajmniej) jednemu z wymienionych wyjątków.
Istnieje pewna dodatkowa subtelność w specyfikacji. Typ e
jest syntetycznym połączeniem typów wyjątków na liście. Gdy używana jest wartość e
, jej typ statyczny jest najmniej powszechnym nadtypem unii typów. Jeśli jednak e
zostanie ponownie wygenerowane w bloku catch
, zgłaszane typy wyjątków to typy w unii. Na przykład:
public void method() throws IOException, SQLException
try {
doSomething();
} catch (IOException | SQLException e) {
report(e);
throw e;
}
Powyżej, IOException
i SQLException
są sprawdzonymi wyjątkami, których najczęstszym nadtypem jest Exception
. Oznacza to, że metoda report
musi pasować do report(Exception)
. Jednak kompilator wie, że throw
może generować tylko SQLException
IOException
lub SQLException
. Tak więc method
można zadeklarować jako throws Exception
throws IOException, SQLException
zamiast throws Exception
. (Co jest dobre: patrz Pitfall - Throwing Throwable, Exception, Error lub RuntimeException .)
Zgłaszanie wyjątku
Poniższy przykład pokazuje podstawy zgłaszania wyjątku:
public void checkNumber(int number) throws IllegalArgumentException {
if (number < 0) {
throw new IllegalArgumentException("Number must be positive: " + number);
}
}
Wyjątek jest zgłaszany na 3. linii. To oświadczenie można podzielić na dwie części:
new IllegalArgumentException(...)
tworzy instancję klasyIllegalArgumentException
z komunikatem opisującym błąd, który zgłasza wyjątek.throw ...
następnie rzuca obiekt wyjątku.
Gdy jest wyjątek, to powoduje, że oświadczenia obejmującego, aby zakończyć nienormalnie aż Wyjątkiem jest obsługiwane. Jest to opisane w innych przykładach.
Dobrą praktyką jest zarówno tworzenie, jak i rzucanie obiektu wyjątku w jednej instrukcji, jak pokazano powyżej. Dobrą praktyką jest również umieszczenie znaczącego komunikatu o błędzie w wyjątku, aby pomóc programiście zrozumieć przyczynę problemu. Jednak niekoniecznie jest to komunikat, który powinien być wyświetlany użytkownikowi końcowemu. (Na początek Java nie ma bezpośredniego wsparcia dla internacjonalizacji komunikatów wyjątków).
Pozostało jeszcze kilka punktów:
Zadeklarowaliśmy
checkNumber
jakothrows IllegalArgumentException
. Nie było to absolutnie konieczne, ponieważIllegalArgumentException
jest sprawdzonym wyjątkiem; zobacz Hierarchia wyjątków Java - niesprawdzone i sprawdzone wyjątki . Dobrą praktyką jest jednak robienie tego, a także uwzględnianie wyjątków zgłaszanych przez metodę javadoc.Kod natychmiast po
throw
oświadczenia jest nieosiągalny. Stąd gdybyśmy to napisali:throw new IllegalArgumentException("it is bad"); return;
kompilator zgłosi błąd kompilacji instrukcji
return
.
Łączenie wyjątków
Wiele standardowych wyjątków ma konstruktor z argumentem drugiej cause
oprócz konwencjonalnego argumentu message
. cause
pozwala łączyć wyjątki. Oto przykład.
Najpierw definiujemy niezaznaczony wyjątek, który zostanie zgłoszony przez naszą aplikację, gdy napotka błąd, którego nie można naprawić. Zauważ, że zawarliśmy konstruktor, który akceptuje argument cause
.
public class AppErrorException extends RuntimeException {
public AppErrorException() {
super();
}
public AppErrorException(String message) {
super(message);
}
public AppErrorException(String message, Throwable cause) {
super(message, cause);
}
}
Następnie oto kod ilustrujący tworzenie łańcuchów wyjątków.
public String readFirstLine(String file) throws AppErrorException {
try (Reader r = new BufferedReader(new FileReader(file))) {
String line = r.readLine();
if (line != null) {
return line;
} else {
throw new AppErrorException("File is empty: " + file);
}
} catch (IOException ex) {
throw new AppErrorException("Cannot read file: " + file, ex);
}
}
throw
w bloku try
wykrywa problem i zgłasza go za pomocą wyjątku z prostym komunikatem. Natomiast throw
w bloku catch
obsługuje wyjątek IOException
poprzez zawinięcie go w nowy (zaznaczony) wyjątek. Jednak nie odrzuca oryginalnego wyjątku. Przekazując IOException
jako cause
, rejestrujemy go, aby można go było wydrukować w pliku tracetrace, jak wyjaśniono w sekcji Tworzenie i czytanie śladów stosu .
Niestandardowe wyjątki
W większości przypadków z punktu widzenia projektowania kodu łatwiej jest używać istniejących ogólnych klas Exception
podczas zgłaszania wyjątków. Jest to szczególnie ważne, jeśli potrzebujesz wyjątku tylko do przenoszenia prostego komunikatu o błędzie. W takim przypadku RuntimeException jest zwykle preferowany, ponieważ nie jest to sprawdzony wyjątek. Istnieją inne klasy wyjątków dla typowych klas błędów:
- UnsupportedOperationException - określona operacja nie jest obsługiwana
- IllegalArgumentException - niepoprawna wartość parametru została przekazana do metody
- IllegalStateException - Twój interfejs API wewnętrznie osiągnął warunek, który nigdy nie powinien się zdarzyć lub który występuje w wyniku nieprawidłowego użycia interfejsu API
Przypadki, w których użytkownik chce używać niestandardowej klasy wyjątków należą:
- Piszesz interfejs API lub bibliotekę do użytku przez innych i chcesz umożliwić użytkownikom interfejsu API przechwytywanie i obsługę wyjątków od interfejsu API oraz odróżniać te wyjątki od innych, bardziej ogólnych wyjątków .
- Zgłaszasz wyjątki dla określonego rodzaju błędu w jednej części programu, który chcesz wychwycić i obsłużyć w innej części programu, i chcesz móc odróżnić te błędy od innych, bardziej ogólnych błędów.
Możesz utworzyć własne niestandardowe wyjątki, rozszerzając RuntimeException
dla niesprawdzonego wyjątku, lub sprawdzając wyjątek, rozszerzając dowolny Exception
który nie jest również podklasą RuntimeException , ponieważ:
Podklasy wyjątków, które nie są również podklasami wyjątku RuntimeException, są sprawdzanymi wyjątkami
public class StringTooLongException extends RuntimeException {
// Exceptions can have methods and fields like other classes
// those can be useful to communicate information to pieces of code catching
// such an exception
public final String value;
public final int maximumLength;
public StringTooLongException(String value, int maximumLength){
super(String.format("String exceeds maximum Length of %s: %s", maximumLength, value));
this.value = value;
this.maximumLength = maximumLength;
}
}
Można ich używać tak, jak z góry określonych wyjątków:
void validateString(String value){
if (value.length() > 30){
throw new StringTooLongException(value, 30);
}
}
A pól można użyć tam, gdzie wyjątek jest wychwytywany i obsługiwany:
void anotherMethod(String value){
try {
validateString(value);
} catch(StringTooLongException e){
System.out.println("The string '" + e.value +
"' was longer than the max of " + e.maximumLength );
}
}
Należy pamiętać, że zgodnie z dokumentacją Java Oracle :
[...] Jeśli można zasadnie oczekiwać, że klient wyzdrowieje z wyjątku, uczyń go sprawdzonym wyjątkiem. Jeśli klient nie może nic zrobić w celu odzyskania od wyjątku, uczyń z niego niesprawdzony wyjątek.
Więcej:
Instrukcja try-with-resources
Ponieważ instrukcja try-catch-final przykład ilustruje, oczyszczanie zasobów przy użyciu finally
klauzuli wymaga znacznej ilości „boiler-plate” kodu w celu realizacji krawędziowe przypadków poprawnie. Java 7 zapewnia znacznie prostszy sposób radzenia sobie z tym problemem w postaci instrukcji try-with-resources .
Co to jest zasób?
Java 7 wprowadziła interfejs java.lang.AutoCloseable
, aby umożliwić zarządzanie klasami za pomocą instrukcji try-with-resources . Wystąpienia klas, które implementują AutoCloseable
są nazywane zasobami . Zazwyczaj należy je utylizować w odpowiednim czasie, zamiast polegać na śmieciarzu w celu ich usunięcia.
Interfejs AutoCloseable
definiuje jedną metodę:
public void close() throws Exception
Metoda close()
powinna zutylizować zasób w odpowiedni sposób. W specyfikacji stwierdzono, że należy bezpiecznie wywołać metodę dla zasobu, który został już zutylizowany. Ponadto, klasy, które implementują Autocloseable
są zdecydowanie zachęcane do zadeklarowania metody close()
aby zgłosić bardziej szczegółowy wyjątek niż Exception
lub w ogóle nie ma wyjątku.
Szeroka gama standardowych klas i interfejsów Java implementuje AutoCloseable
. Obejmują one:
-
InputStream
,OutputStream
i ich podklasy -
Reader
,Writer
i ich podklasy -
Socket
andServerSocket
i ich podklasy -
Channel
i jego podklasy, oraz - interfejsy JDBC
Connection
,Statement
iResultSet
oraz ich podklasy.
Mogą to zrobić również klasy aplikacji i klasy zewnętrzne.
Podstawowa instrukcja try-with-resource
Składnia try-with-zasobów oparty jest na klasycznej try-catch, try-w końcu i try-catch-finally formy. Oto przykład „podstawowej” formy; tzn. formularz bez catch
lub finally
.
try (PrintStream stream = new PrintStream("hello.txt")) {
stream.println("Hello world!");
}
Zasoby, którymi należy zarządzać, są zadeklarowane jako zmienne w sekcji (...)
po klauzuli try
. W powyższym przykładzie deklarujemy stream
zmiennej zasobu i inicjalizujemy go w nowo utworzonym PrintStream
.
Po zainicjowaniu zmiennych zasobów wykonywany jest blok try
. Po zakończeniu stream.close()
zostanie automatycznie wywołana, aby zapewnić, że zasób nie wycieknie. Zauważ, że wywołanie close()
ma miejsce bez względu na to, jak zakończy się blok.
Ulepszone instrukcje try-with-resource
Instrukcja try-with-resources może być ulepszona za pomocą catch
i finally
bloków, tak jak w składni try-catch- last w wersji wcześniejszej niż Java 7. Poniższy fragment kodu dodaje blok catch
do naszego poprzedniego w celu obsługi FileNotFoundException
który może PrintStream
konstruktor PrintStream
:
try (PrintStream stream = new PrintStream("hello.txt")) {
stream.println("Hello world!");
} catch (FileNotFoundException ex) {
System.err.println("Cannot open the file");
} finally {
System.err.println("All done");
}
Jeśli inicjalizacja zasobu lub blok try zgłasza wyjątek, blok catch
zostanie wykonany. Blok w finally
zawsze będzie wykonywany, podobnie jak w przypadku konwencjonalnej instrukcji try-catch-last .
Warto jednak zwrócić uwagę na kilka rzeczy:
- Zmienna zasobu jest poza zakresem w
catch
ifinally
blokuje. - Czyszczenie zasobów nastąpi zanim instrukcja spróbuje dopasować blok
catch
. - Jeśli automatyczne czyszczenie zasobów wygenerowało wyjątek, może on zostać przechwycony w jednym z bloków
catch
.
Zarządzanie wieloma zasobami
Fragmenty kodu powyżej pokazują pojedynczy zarządzany zasób. W rzeczywistości try-with-resources może zarządzać wieloma zasobami w jednej instrukcji. Na przykład:
try (InputStream is = new FileInputStream(file1);
OutputStream os = new FileOutputStream(file2)) {
// Copy 'is' to 'os'
}
Zachowuje się tak, jak można się spodziewać. Zarówno is
i os
są automatycznie zamykane na końcu bloku try
. Należy zwrócić uwagę na kilka punktów:
- Inicjalizacje następują w kolejności kodu, a później inicjalizatory zmiennych zasobów mogą korzystać z wartości wcześniejszych.
- Wszystkie zmienne zasobów, które zostały pomyślnie zainicjowane, zostaną wyczyszczone.
- Zmienne zasobów są czyszczone w odwrotnej kolejności do ich deklaracji.
Tak więc, w powyższym przykładzie, is
inicjowany jest przed os
i oczyścić za nim i is
zostaną oczyszczone, jeżeli jest wyjątek podczas inicjalizacji os
.
Równoważność try-with-resource i klasycznego try-catch-wreszcie
Specyfikacja języka Java określa zachowanie formularzy try-with-resource w kategoriach klasycznej instrukcji try-catch-wreszcie . (Szczegółowe informacje znajdują się w JLS.)
Na przykład ta podstawowa próba z zasobem :
try (PrintStream stream = new PrintStream("hello.txt")) {
stream.println("Hello world!");
}
jest zdefiniowany jako równoważny temu try-catch-wreszcie :
// Note that the constructor is not part of the try-catch statement
PrintStream stream = new PrintStream("hello.txt");
// This variable is used to keep track of the primary exception thrown
// in the try statement. If an exception is thrown in the try block,
// any exception thrown by AutoCloseable.close() will be suppressed.
Throwable primaryException = null;
// The actual try block
try {
stream.println("Hello world!");
} catch (Throwable t) {
// If an exception is thrown, remember it for the finally block
primaryException = t;
throw t;
} finally {
if (primaryException == null) {
// If no exception was thrown so far, exceptions thrown in close() will
// not be caught and therefore be passed on to the enclosing code.
stream.close();
} else {
// If an exception has already been thrown, any exception thrown in
// close() will be suppressed as it is likely to be related to the
// previous exception. The suppressed exception can be retrieved
// using primaryException.getSuppressed().
try {
stream.close();
} catch (Throwable suppressedException) {
primaryException.addSuppressed(suppressedException);
}
}
}
(JLS określa, że rzeczywiste zmienne t
i primaryException
będą niewidoczne dla normalnego kodu Java.)
Ulepszona forma wypróbowania z zasobami jest określona jako równoważność z formą podstawową. Na przykład:
try (PrintStream stream = new PrintStream(fileName)) {
stream.println("Hello world!");
} catch (NullPointerException ex) {
System.err.println("Null filename");
} finally {
System.err.println("All done");
}
jest równa:
try {
try (PrintStream stream = new PrintStream(fileName)) {
stream.println("Hello world!");
}
} catch (NullPointerException ex) {
System.err.println("Null filename");
} finally {
System.err.println("All done");
}
Tworzenie i czytanie śladów stosu
Gdy tworzony jest obiekt wyjątku (tj. Gdy jest new
), konstruktor Throwable
przechwytuje informacje o kontekście, w którym wyjątek został utworzony. Później informacje te mogą być wyprowadzane w postaci stacktrace, którego można użyć do zdiagnozowania problemu, który spowodował wyjątek.
Drukowanie śladu stosu
Wydrukowanie stacktrace to po prostu kwestia wywołania metody printStackTrace()
. Na przykład:
try {
int a = 0;
int b = 0;
int c = a / b;
} catch (ArithmeticException ex) {
// This prints the stacktrace to standard output
ex.printStackTrace();
}
Metoda printStackTrace()
bez argumentów wypisuje na standardowe wyjście aplikacji; tzn. bieżący System.out
. Istnieją również printStackTrace(PrintStream)
i printStackTrace(PrintWriter)
które drukują do określonego Stream
lub Writer
.
Uwagi:
Stacktrace nie zawiera szczegółów samego wyjątku. Możesz użyć metody
toString()
, aby uzyskać te szczegóły; na przykład// Print exception and stacktrace System.out.println(ex); ex.printStackTrace();
Drukowanie Stacktrace powinno być stosowane oszczędnie; patrz Pitfall - Nadmierne lub nieodpowiednie stosy śladów . Często lepiej jest użyć struktury rejestrowania i przekazać obiekt wyjątku do zarejestrowania.
Zrozumienie śledzenia stosu
Rozważ następujący prosty program składający się z dwóch klas w dwóch plikach. (Pokazaliśmy nazwy plików i dodaliśmy numery wierszy w celach ilustracyjnych).
File: "Main.java"
1 public class Main {
2 public static void main(String[] args) {
3 new Test().foo();
4 }
5 }
File: "Test.java"
1 class Test {
2 public void foo() {
3 bar();
4 }
5
6 public int bar() {
7 int a = 1;
8 int b = 0;
9 return a / b;
10 }
Po skompilowaniu i uruchomieniu tych plików otrzymamy następujące dane wyjściowe.
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Test.bar(Test.java:9)
at Test.foo(Test.java:3)
at Main.main(Main.java:3)
Przeczytajmy ten wiersz po kolei, aby dowiedzieć się, co nam mówi.
Wiersz nr 1 mówi nam, że wątek o nazwie „main” zakończył się z powodu nieprzechwyconego wyjątku. Pełna nazwa wyjątku to java.lang.ArithmeticException
, a komunikat o wyjątku to „/ zero”.
Jeśli sprawdzimy javadocs pod kątem tego wyjątku, to powie:
Wyrzucany, gdy wystąpi wyjątkowy warunek arytmetyczny. Na przykład liczba całkowita „dziel przez zero” powoduje wystąpienie tej klasy.
Rzeczywiście, komunikat „/ przez zero” jest mocną wskazówką, że przyczyną wyjątku jest to, że jakiś kod próbował podzielić coś przez zero. Ale co?
Pozostałe 3 linie to ślad stosu. Każda linia reprezentuje wywołanie metody (lub konstruktora) na stosie wywołań, a każda z nich mówi nam trzy rzeczy:
- nazwa klasy i metody, która była wykonywana,
- nazwa pliku kodu źródłowego,
- numer wiersza kodu źródłowego wykonywanej instrukcji
Te linie stacktrace są wymienione na górze ramki dla bieżącego połączenia. Górna ramka w powyższym przykładzie znajduje się w metodzie Test.bar
i w wierszu 9 pliku Test.java. To jest następujący wiersz:
return a / b;
Jeśli spojrzymy na kilka linii wcześniej w pliku, w którym inicjowane jest b
, oczywiste jest, że b
będzie miało wartość zero. Bez wątpienia możemy powiedzieć, że jest to przyczyną wyjątku.
Gdybyśmy musieli pójść dalej, możemy zobaczyć z stacktrace, że bar()
został wywołany z foo()
w linii 3 Test.java, a to z kolei foo()
zostało wywołane z Main.main()
.
Uwaga: Nazwy klas i metod w ramkach stosu to wewnętrzne nazwy klas i metod. Musisz rozpoznać następujące nietypowe przypadki:
- Zagnieżdżona lub wewnętrzna klasa będzie wyglądać jak „OuterClass $ InnerClass”.
- Anonimowa klasa wewnętrzna będzie wyglądać jak „OuterClass $ 1”, „OuterClass $ 2” itp.
- Gdy wykonywany jest kod w konstruktorze, inicjalizatorze pola instancji lub bloku inicjującym instancję, nazwą metody będzie „”.
- Gdy wykonywany jest kod w inicjalizatorze pola statycznego lub bloku inicjatora statycznego, nazwą metody będzie „”.
(W niektórych wersjach Java kod formatowania stacktrace wykryje i obejmie powtarzające się sekwencje ramek stosu, co może się zdarzyć, gdy aplikacja zawiedzie z powodu nadmiernej rekurencji.)
Łączenie wyjątków i zagnieżdżone ślady stosu
Łączenie wyjątków ma miejsce, gdy fragment kodu przechwytuje wyjątek, a następnie tworzy i zgłasza nowy, przekazując pierwszy wyjątek jako przyczynę. Oto przykład:
File: Test,java
1 public class Test {
2 int foo() {
3 return 0 / 0;
4 }
5
6 public Test() {
7 try {
8 foo();
9 } catch (ArithmeticException ex) {
10 throw new RuntimeException("A bad thing happened", ex);
11 }
12 }
13
14 public static void main(String[] args) {
15 new Test();
16 }
17 }
Gdy powyższa klasa zostanie skompilowana i uruchomiona, otrzymujemy następujący stacktrace:
Exception in thread "main" java.lang.RuntimeException: A bad thing happened
at Test.<init>(Test.java:10)
at Test.main(Test.java:15)
Caused by: java.lang.ArithmeticException: / by zero
at Test.foo(Test.java:3)
at Test.<init>(Test.java:8)
... 1 more
Stacktrace zaczyna się od nazwy klasy, metody i stosu wywołań wyjątku, który (w tym przypadku) spowodował awarię aplikacji. Następnie następuje wiersz „Przyczyna:”, który zgłasza wyjątek cause
. Podawana jest nazwa klasy i komunikat, a następnie ramki stosu wyjątku przyczyny. Śledzenie kończy się na „... N więcej”, co oznacza, że ostatnie N ramek jest takich samych jak w poprzednim wyjątku.
„Przyczyna przez:” jest uwzględniana w danych wyjściowych tylko wtedy, gdy podstawowa cause
wyjątku nie jest null
). Wyjątki można łączyć w łańcuchy na czas nieokreślony, w takim przypadku ślad stosu może mieć wiele śladów „Spowodowane przez:”.
Uwaga: mechanizm cause
został ujawniony tylko w interfejsie API Throwable
w Javie 1.4.0. Przed tym aplikacja musiała zaimplementować tworzenie łańcuchów wyjątków za pomocą niestandardowego pola wyjątku reprezentującego przyczynę oraz niestandardowej metody printStackTrace
.
Przechwytywanie śledzenia stosu jako ciągu
Czasami aplikacja musi być w stanie przechwycić ślad stosu jako String
Java, aby można go było wykorzystać do innych celów. Ogólne podejście do tego polega na utworzeniu tymczasowego OutputStream
lub Writer
który zapisuje w buforze pamięci i przekazuje go do printStackTrace(...)
.
Biblioteki Apache Commons i Guava zapewniają metody użytkowe do przechwytywania śledzenia stosu jako łańcucha:
org.apache.commons.lang.exception.ExceptionUtils.getStackTrace(Throwable)
com.google.common.base.Throwables.getStackTraceAsString(Throwable)
Jeśli nie możesz używać bibliotek stron trzecich w bazie kodu, wykonaj następującą metodę z wykonaj zadanie:
/**
* Returns the string representation of the stack trace.
*
* @param throwable the throwable
* @return the string.
*/
public static String stackTraceToString(Throwable throwable) {
StringWriter stringWriter = new StringWriter();
throwable.printStackTrace(new PrintWriter(stringWriter));
return stringWriter.toString();
}
Zauważ, że jeśli zamierzasz analizować stacktrace, łatwiej jest użyć getStackTrace()
i getCause()
niż próbować parsować stacktrace.
Obsługa wyjątku InterruptedException
InterruptedException
jest mylącą bestią - pojawia się w pozornie nieszkodliwych metodach, takich jak Thread.sleep()
, ale niepoprawne obchodzenie się z nią prowadzi do trudnego do zarządzania kodu, który źle działa w współbieżnych środowiskach.
W najprostszym przypadku, jeśli zostanie przechwycony Thread.interrupt()
InterruptedException
, oznacza to, że ktoś, gdzieś, o nazwie Thread.interrupt()
w wątku, w którym aktualnie działa kod. Możesz Thread.interrupt()
się powiedzieć: „To mój kod! Nigdy go nie przerwę! „ i dlatego zrób coś takiego:
// Bad. Don't do this.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// disregard
}
Ale to jest właśnie niewłaściwy sposób radzenia sobie z wydarzeniem „niemożliwym”. Jeśli wiesz, że Twoja aplikacja nigdy nie spotka się z InterruptedException
, powinieneś potraktować takie zdarzenie jako poważne naruszenie założeń programu i wyjść tak szybko, jak to możliwe.
Właściwy sposób obsługi „niemożliwego” przerwania jest następujący:
// When nothing will interrupt your code
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new AssertionError(e);
}
To robi dwie rzeczy; najpierw przywraca status przerwania wątku (tak jakby przede wszystkim nie zgłoszono AssertionError
InterruptedException
), a następnie zgłasza błąd AssertionError
wskazujący, że zostały naruszone podstawowe niezmienniki aplikacji. Jeśli wiesz na pewno, że nigdy nie przerwiesz wątku, w którym działa ten kod, jest bezpieczny, ponieważ blok catch
nigdy nie powinien zostać osiągnięty.
Korzystanie z klasy Uninterruptibles
Guava pomaga uprościć ten wzór; wywołanie Uninterruptibles.sleepUninterruptibly()
ignoruje przerwany stan wątku aż do upływu czasu uśpienia (w tym momencie jest on przywracany do późniejszych wywołań w celu sprawdzenia i zgłoszenia własnego InterruptedException
). Jeśli wiesz, że nigdy nie przerwiesz takiego kodu, bezpiecznie unikasz konieczności zawijania połączeń snu w bloku try-catch.
Częściej jednak nie możesz zagwarantować, że Twój wątek nigdy nie zostanie przerwany. W szczególności, jeśli piszesz kod, który zostanie wykonany przez Executor
lub inne zarządzanie wątkami, bardzo ważne jest, aby Twój kod szybko reagował na przerwania, w przeciwnym razie twoja aplikacja utknie, a nawet zablokuje się.
W takich przypadkach najlepiej jest pozwolić InterruptedException
na propagację stosu wywołań, dodając po kolei throws InterruptedException
do każdej metody. Może się to wydawać nieprzyzwoite, ale w rzeczywistości jest pożądaną właściwością - podpisy twojej metody wskazują teraz dzwoniącym, że natychmiast zareaguje na przerwania.
// Let the caller determine how to handle the interrupt if you're unsure
public void myLongRunningMethod() throws InterruptedException {
...
}
W ograniczonych przypadkach (np. Podczas przesłonięcia metody, która nie throw
żadnych sprawdzonych wyjątków), możesz zresetować status przerwania bez zgłaszania wyjątku, oczekując, że dowolny kod zostanie wykonany obok tej przeróbki. Opóźnia to obsługę przerwania, ale nie tłumi go całkowicie.
// Suppresses the exception but resets the interrupted state letting later code
// detect the interrupt and handle it properly.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return ...; // your expectations are still broken at this point - try not to do more work.
}
Hierarchia wyjątków Java - niesprawdzone i sprawdzone wyjątki
Wszystkie wyjątki Java są instancjami klas w hierarchii klas wyjątków. Można to przedstawić następująco:
-
java.lang.Throwable
- To jest klasa podstawowa dla wszystkich klas wyjątków. Jego metody i konstruktory implementują szereg funkcji wspólnych dla wszystkich wyjątków.-
java.lang.Exception
- To jest nadklasa wszystkich normalnych wyjątków.- różne standardowe i niestandardowe klasy wyjątków.
-
java.lang.RuntimeException
- To jest nadklasa wszystkich normalnych wyjątków, które są niesprawdzonymi wyjątkami .- różne standardowe i niestandardowe klasy wyjątków czasu wykonywania.
-
java.lang.Error
- To jest nadklasa wszystkich wyjątków „błąd krytyczny”.
-
Uwagi:
- Rozróżnienie między sprawdzonymi i niesprawdzonymi wyjątkami opisano poniżej.
- Klasa
Throwable
,Exception
iRuntimeException
należy traktować jakoabstract
; patrz Pitfall - Throwing Throwable, Exception, Error lub RuntimeException . - JVM zgłasza wyjątki dotyczące
Error
w sytuacjach, w których próba odzyskania aplikacji byłaby niebezpieczna lub nierozsądna. - Rozsądne byłoby deklarowanie niestandardowych podtypów
Throwable
. Narzędzia i biblioteki Java mogą zakładać, żeError
iException
są jedynymi bezpośrednimi podtypamiThrowable
i źle się zachowują, jeśli to założenie jest nieprawidłowe.
Sprawdzone kontra niesprawdzone wyjątki
Jedną z krytycznych uwag dotyczących obsługi wyjątków w niektórych językach programowania jest trudność w określeniu, które wyjątki może wywołać dana metoda lub procedura. Biorąc pod uwagę, że nieobsługiwany wyjątek może spowodować awarię programu, może to uczynić wyjątki źródłem niestabilności.
Język Java rozwiązuje ten problem za pomocą sprawdzonego mechanizmu wyjątków. Po pierwsze, Java dzieli wyjątki na dwie kategorie:
Sprawdzone wyjątki zazwyczaj przedstawiają przewidywane zdarzenia, z którymi aplikacja powinna być w stanie sobie poradzić. Na przykład
IOException
i jego podtypy reprezentują warunki błędu, które mogą wystąpić w operacjach we / wy. Przykłady obejmują niepowodzenie otwierania pliku, ponieważ plik lub katalog nie istnieje, sieć odczytuje i zapisuje błąd, ponieważ połączenie sieciowe zostało zerwane i tak dalej.Niesprawdzone wyjątki zazwyczaj reprezentują nieprzewidziane zdarzenia, z którymi aplikacja nie może sobie poradzić. Zazwyczaj są one wynikiem błędu w aplikacji.
(W dalszej części „wyrzucony” odnosi się do każdego wyjątku zgłoszonego jawnie (przez instrukcję throw
) lub niejawnie (w przypadku nieudanego odwołania, rzutowania typu itd.). Podobnie „propagowane” odnosi się do wyjątku, który został zgłoszony w zagnieżdżone połączenie, które nie jest przechwytywane w ramach tego połączenia. Przykładowy kod poniżej to zilustruje.)
Druga część mechanizmu sprawdzanego wyjątku polega na tym, że istnieją ograniczenia dotyczące metod, w których może wystąpić sprawdzony wyjątek:
- Kiedy sprawdzany jest wyjątek albo rozmnaża w sposobie, należy albo złapać sposobu, czy też nie w sposób na
throws
punktu. (Znaczenie klauzulithrows
opisano w tym przykładzie ). - Gdy sprawdzony wyjątek jest zgłaszany lub propagowany w bloku inicjalizującym, musi zostać przechwycony blok.
- Zaznaczonego wyjątku nie można propagować za pomocą wywołania metody w wyrażeniu inicjalizacji pola. (Nie ma sposobu, aby złapać taki wyjątek.)
Krótko mówiąc, sprawdzony wyjątek musi być obsłużony lub zadeklarowany.
Ograniczenia te nie mają zastosowania do niesprawdzonych wyjątków. Dotyczy to wszystkich przypadków, w których wyjątek jest zgłaszany niejawnie, ponieważ wszystkie takie przypadki zgłaszają niesprawdzone wyjątki.
Sprawdzone przykłady wyjątków
Te fragmenty kodu mają na celu zilustrowanie sprawdzonych ograniczeń wyjątków. W każdym przypadku pokazujemy wersję kodu z błędem kompilacji oraz drugą wersję z poprawionym błędem.
// This declares a custom checked exception.
public class MyException extends Exception {
// constructors omitted.
}
// This declares a custom unchecked exception.
public class MyException2 extends RuntimeException {
// constructors omitted.
}
Pierwszy przykład pokazuje, jak jawnie zgłoszone sprawdzone wyjątki mogą zostać zadeklarowane jako „zgłoszone”, jeśli nie powinny być obsługiwane w metodzie.
// INCORRECT
public void methodThrowingCheckedException(boolean flag) {
int i = 1 / 0; // Compiles OK, throws ArithmeticException
if (flag) {
throw new MyException(); // Compilation error
} else {
throw new MyException2(); // Compiles OK
}
}
// CORRECTED
public void methodThrowingCheckedException(boolean flag) throws MyException {
int i = 1 / 0; // Compiles OK, throws ArithmeticException
if (flag) {
throw new MyException(); // Compilation error
} else {
throw new MyException2(); // Compiles OK
}
}
Drugi przykład pokazuje, w jaki sposób można rozwiązać propagowany sprawdzony wyjątek.
// INCORRECT
public void methodWithPropagatedCheckedException() {
InputStream is = new FileInputStream("someFile.txt"); // Compilation error
// FileInputStream throws IOException or a subclass if the file cannot
// be opened. IOException is a checked exception.
...
}
// CORRECTED (Version A)
public void methodWithPropagatedCheckedException() throws IOException {
InputStream is = new FileInputStream("someFile.txt");
...
}
// CORRECTED (Version B)
public void methodWithPropagatedCheckedException() {
try {
InputStream is = new FileInputStream("someFile.txt");
...
} catch (IOException ex) {
System.out.println("Cannot open file: " + ex.getMessage());
}
}
Ostatni przykład pokazuje, jak postępować z zaznaczonym wyjątkiem w inicjalizatorze pola statycznego.
// INCORRECT
public class Test {
private static final InputStream is =
new FileInputStream("someFile.txt"); // Compilation error
}
// CORRECTED
public class Test {
private static final InputStream is;
static {
InputStream tmp = null;
try {
tmp = new FileInputStream("someFile.txt");
} catch (IOException ex) {
System.out.println("Cannot open file: " + ex.getMessage());
}
is = tmp;
}
}
Należy zauważyć, że w tym ostatnim przypadku mamy również do czynienia z problemami, które is
nie mogą być przypisane do więcej niż raz, i jeszcze także musi być przypisany do, nawet w przypadku wystąpienia wyjątku.
Wprowadzenie
Wyjątkiem są błędy, które występują podczas wykonywania programu. Rozważmy program Java, który dzieli dwie liczby całkowite.
class Division {
public static void main(String[] args) {
int a, b, result;
Scanner input = new Scanner(System.in);
System.out.println("Input two integers");
a = input.nextInt();
b = input.nextInt();
result = a / b;
System.out.println("Result = " + result);
}
}
Teraz kompilujemy i wykonujemy powyższy kod i widzimy dane wyjściowe dla próby dzielenia przez zero:
Input two integers
7 0
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Division.main(Disivion.java:14)
Dzielenie przez zero jest niepoprawną operacją, która wygenerowałaby wartość, której nie można przedstawić jako liczbę całkowitą. Java radzi sobie z tym, zgłaszając wyjątek . W tym przypadku wyjątkiem jest instancja klasy ArithmeticException .
Uwaga: Przykład tworzenia i odczytywania śladów stosu wyjaśnia, co oznaczają dane wyjściowe po dwóch liczbach.
Użytecznością wyjątku jest dozwolona kontrola przepływu. Bez wyjątków typowym rozwiązaniem tego problemu może być najpierw sprawdzenie, czy b == 0
:
class Division {
public static void main(String[] args) {
int a, b, result;
Scanner input = new Scanner(System.in);
System.out.println("Input two integers");
a = input.nextInt();
b = input.nextInt();
if (b == 0) {
System.out.println("You cannot divide by zero.");
return;
}
result = a / b;
System.out.println("Result = " + result);
}
}
Spowoduje to wydrukowanie komunikatu You cannot divide by zero.
do konsoli i kończy program w sposób wdzięczny, gdy użytkownik próbuje podzielić przez zero. Równoważnym sposobem radzenia sobie z tym problemem poprzez obsługę wyjątków byłoby zastąpienie kontroli if
flow blokiem try-catch
:
...
a = input.nextInt();
b = input.nextInt();
try {
result = a / b;
}
catch (ArithmeticException e) {
System.out.println("An ArithmeticException occurred. Perhaps you tried to divide by zero.");
return;
}
...
Blok try catch jest wykonywany w następujący sposób:
- Rozpocznij wykonywanie kodu w bloku
try
. - Jeśli wyjątek wystąpi w bloku try, natychmiast przerwij i sprawdź, czy wyjątek ten jest przechwytywany przez blok
catch
(w tym przypadku, gdy wyjątek jest wystąpieniem wyjątkuArithmeticException
). - Jeśli wyjątek zostanie przechwycony , zostaje przypisany do zmiennej
e
i wykonywany jest blokcatch
. - Jeśli blok
try
lubcatch
zostanie zakończony (tzn. Podczas wykonywania kodu nie wystąpią nieprzechwycone wyjątki), kontynuuj wykonywanie kodu poniżej blokutry-catch
.
Powszechnie uważa się, że dobrą praktyką jest stosowanie obsługi wyjątków jako części normalnej kontroli przepływu aplikacji, w której zachowanie byłoby niezdefiniowane lub nieoczekiwane. Na przykład, zamiast zwracać null
gdy metoda zawiedzie, zwykle lepszą praktyką jest zgłoszenie wyjątku, aby aplikacja korzystająca z tej metody mogła zdefiniować własną kontrolę przepływu dla sytuacji za pomocą obsługi wyjątków, jak pokazano powyżej. W pewnym sensie omija to problem zwrócenia określonego typu , ponieważ może zostać zgłoszony jeden z wielu rodzajów wyjątków wskazujących na konkretny problem, który wystąpił.
Aby uzyskać więcej porad na temat tego, jak i jak nie używać wyjątków, zobacz Pułapki Java - użycie wyjątku
Zwróć instrukcje w bloku try catch
Chociaż jest to zła praktyka, możliwe jest dodanie wielu instrukcji return w bloku obsługi wyjątków:
public static int returnTest(int number){
try{
if(number%2 == 0) throw new Exception("Exception thrown");
else return x;
}
catch(Exception e){
return 3;
}
finally{
return 7;
}
}
Ta metoda zawsze zwróci 7, ponieważ blok w końcu powiązany z blokiem try / catch jest wykonywany przed zwróceniem czegokolwiek. Teraz, jak w końcu ma return 7;
, ta wartość zastępuje wartości zwracane try / catch.
Jeśli blok catch zwróci wartość pierwotną, a ta pierwotna wartość zostanie następnie zmieniona w bloku catch, wartość zwrócona w bloku catch zostanie zwrócona, a zmiany z bloku last zostaną zignorowane.
Poniższy przykład wyświetli „0”, a nie „1”.
public class FinallyExample {
public static void main(String[] args) {
int n = returnTest(4);
System.out.println(n);
}
public static int returnTest(int number) {
int returnNumber = 0;
try {
if (number % 2 == 0)
throw new Exception("Exception thrown");
else
return returnNumber;
} catch (Exception e) {
return returnNumber;
} finally {
returnNumber = 1;
}
}
}
Zaawansowane funkcje wyjątków
Ten przykład obejmuje niektóre zaawansowane funkcje i przypadki użycia wyjątków.
Programowe sprawdzanie stosu wywołań
Podstawowym zastosowaniem wyjątków stosów śledzenia jest dostarczenie informacji o błędzie aplikacji i jej kontekście, aby programiści mogli zdiagnozować i naprawić problem. Czasami można go wykorzystać do innych celów. Na przykład klasa SecurityManager
może potrzebować zbadać stos wywołań, aby zdecydować, czy kod, który wykonuje połączenie, powinien być zaufany.
Możesz użyć wyjątków, aby programowo zbadać stos wywołań w następujący sposób:
Exception ex = new Exception(); // this captures the call stack
StackTraceElement[] frames = ex.getStackTrace();
System.out.println("This method is " + frames[0].getMethodName());
System.out.println("Called from method " + frames[1].getMethodName());
Jest kilka ważnych zastrzeżeń:
Informacje dostępne w
StackTraceElement
są ograniczone. Nie ma więcej dostępnych informacji, niż wyświetlaprintStackTrace
. (Wartości zmiennych lokalnych w ramce nie są dostępne.)getStackTrace()
dlagetStackTrace()
stwierdzają, że JVM może pominąć ramki:Niektóre maszyny wirtualne mogą w pewnych okolicznościach pomijać jedną lub więcej ramek stosu ze śledzenia stosu. W skrajnym przypadku maszyna wirtualna, która nie ma informacji o stosie dotyczących tego narzędzia do rzucania, może zwrócić tablicę o zerowej długości z tej metody.
Optymalizacja konstrukcji wyjątków
Jak wspomniano w innym miejscu, konstruowanie wyjątku jest dość drogie, ponieważ wymaga przechwytywania i rejestrowania informacji o wszystkich ramkach stosu w bieżącym wątku. Czasami wiemy, że ta informacja nigdy nie zostanie wykorzystana dla danego wyjątku; np. stacktrace nigdy nie zostanie wydrukowany. W takim przypadku istnieje sztuczka implementacyjna, której możemy użyć w niestandardowym wyjątku, aby uniemożliwić przechwycenie informacji.
Informacje o ramce stosu potrzebne do śledzenia stosów są przechwytywane, gdy konstruktory Throwable
wywołują metodę Throwable.fillInStackTrace()
. Ta metoda jest public
, co oznacza, że podklasa może ją zastąpić. Sztuką jest zastąpienie metody odziedziczonej po Throwable
taką, która nic nie robi; na przykład
public class MyException extends Exception {
// constructors
@Override
public void fillInStackTrace() {
// do nothing
}
}
Problem z tym podejściem polega na tym, że wyjątek, który zastępuje fillInStackTrace()
nigdy nie może przechwycić stacktrace i jest bezużyteczny w scenariuszach, w których jest potrzebny.
Kasowanie lub wymiana stacktrace
W niektórych sytuacjach ślad stosu dla wyjątku utworzonego w normalny sposób zawiera niepoprawne informacje lub informacje, których deweloper nie chce ujawniać użytkownikowi. W tych scenariuszach można użyć Throwable.setStackTrace
aby zastąpić tablicę obiektów StackTraceElement
która przechowuje informacje.
Na przykład następujące informacje mogą zostać użyte do odrzucenia informacji o stosie wyjątku:
exception.setStackTrace(new StackTraceElement[0]);
Wyłączone wyjątki
Java 7 wprowadziła konstrukcję try-with-resources i powiązaną koncepcję eliminacji wyjątków. Rozważ następujący fragment kodu:
try (Writer w = new BufferedWriter(new FileWriter(someFilename))) {
// do stuff
int temp = 0 / 0; // throws an ArithmeticException
}
Kiedy zostanie zgłoszony wyjątek, try
wywoła close()
na w
który opróżni buforowane wyjście, a następnie zamknie FileWriter
. Ale co się stanie, jeśli zostanie wygenerowany IOException
podczas opróżniania wyjścia?
Dzieje się tak, że wszelkie wyjątki zgłaszane podczas czyszczenia zasobu są pomijane . Wyjątek jest wychwytywany i dodawany do listy wyłączonych wyjątków podstawowych. Następnie try-with-resources będzie kontynuowane z czyszczeniem innych zasobów. Wreszcie główny wyjątek zostanie ponownie wprowadzony.
Podobny wzorzec występuje, gdy wyjątek jest rzucony podczas inicjalizacji zasobów, lub jeśli try
Blok kończy się normalnie. Pierwszy zgłoszony wyjątek staje się głównym wyjątkiem, a kolejne wynikające z czyszczenia są pomijane.
getSuppressedExceptions
wyjątki można pobrać z podstawowego obiektu wyjątku, wywołując getSuppressedExceptions
.
Instrukcje try-last i try-catch-wreszcie
Instrukcja try...catch...finally
łączy obsługę wyjątków z kodem czyszczenia. finally
blok zawiera kod, który zostanie wykonany we wszystkich okolicznościach. Dzięki temu nadają się do zarządzania zasobami i innych rodzajów czyszczenia.
Spróbuj w końcu
Oto przykład prostszej ( try...finally
) formy:
try {
doSomething();
} finally {
cleanUp();
}
Zachowanie try...finally
wygląda następująco:
- Kod w bloku
try
jest wykonywany. - Jeśli w bloku
try
nie został zgłoszony wyjątek:- Kod w
finally
bloku jest wykonywany. - Jeśli w
finally
blok zgłosi wyjątek, wyjątek ten jest propagowany. - W przeciwnym razie kontrola przechodzi do następnej instrukcji po
try...finally
.
- Kod w
- Jeśli wyjątek został zgłoszony w bloku try:
- Kod w
finally
bloku jest wykonywany. - Jeśli w
finally
blok zgłosi wyjątek, wyjątek ten jest propagowany. - W przeciwnym razie oryginalny wyjątek będzie kontynuowany.
- Kod w
Kod w finally
bloku zawsze będzie wykonywany. (Jedynymi wyjątkami są System.exit(int)
lub panika JVM.) Tak więc finally
blok jest poprawnym kodem miejsca, który zawsze musi zostać wykonany; np. zamykanie plików i innych zasobów lub zwalnianie blokad.
spróbuj złapać w końcu
Nasz drugi przykład pokazuje, w jaki sposób catch
i finally
mogą być używane razem. Pokazuje również, że czyszczenie zasobów nie jest proste.
// This code snippet writes the first line of a file to a string
String result = null;
Reader reader = null;
try {
reader = new BufferedReader(new FileReader(fileName));
result = reader.readLine();
} catch (IOException ex) {
Logger.getLogger.warn("Unexpected IO error", ex); // logging the exception
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ex) {
// ignore / discard this exception
}
}
}
Pełny zestaw (hipotetycznych) zachowań try...catch...finally
w tym przykładzie jest zbyt skomplikowany, aby go tutaj opisać. Prosta wersja polega na tym, że kod w finally
bloku zawsze będzie wykonywany.
Patrząc na to z perspektywy zarządzania zasobami:
- Deklarujemy „zasób” (tj. Zmienną
reader
) przed blokiemtry
, aby znalazł się w zakresie dla blokufinally
. - Umieszczając
new FileReader(...)
,catch
może obsłużyć każdy wyjątekIOError
new FileReader(...)
podczas otwierania pliku. - Potrzebujemy
reader.close()
wfinally
bloku, ponieważ istnieją pewne ścieżki wyjątków, których nie możemy przechwycić ani w blokutry
, ani w blokucatch
. - Ponieważ jednak wyjątek mógł zostać zgłoszony przed zainicjowaniem
reader
, potrzebujemy również jawnego testunull
. - Wreszcie
reader.close()
może (hipotetycznie)reader.close()
wyjątek. Nie dbamy o to, ale jeśli nie złapiemy wyjątku u źródła, będziemy musieli sobie z tym poradzić dalej na stosie wywołań.
Java 7 i nowsze wersje zapewniają alternatywną składnię try-with-resources, która znacznie upraszcza czyszczenie zasobów.
Klauzula „rzuca” w deklaracji metody
Mechanizm sprawdzanego wyjątku Java wymaga od programisty oświadczenia, że niektóre metody mogą zgłaszać określone sprawdzone wyjątki. Odbywa się to za pomocą klauzuli throws
. Na przykład:
public class OddNumberException extends Exception { // a checked exception
}
public void checkEven(int number) throws OddNumberException {
if (number % 2 != 0) {
throw new OddNumberException();
}
}
throws OddNumberException
deklaruje, że wywołanie checkEven
może checkEven
wyjątek typu OddNumberException
.
Klauzula throws
może zadeklarować listę typów i może zawierać niezaznaczone wyjątki, a także sprawdzone wyjątki.
public void checkEven(Double number)
throws OddNumberException, ArithmeticException {
if (!Double.isFinite(number)) {
throw new ArithmeticException("INF or NaN");
} else if (number % 2 != 0) {
throw new OddNumberException();
}
}
Po co ogłaszać niezaznaczone wyjątki jako zgłoszone?
Klauzula throws
w deklaracji metody służy dwóm celom:
Informuje kompilator, które wyjątki są zgłaszane, aby kompilator mógł zgłaszać nieprzechwycone (zaznaczone) wyjątki jako błędy.
Informuje programistę, który pisze kod wywołujący metodę, jakich wyjątków się spodziewać. W tym celu często ma sens umieszczenie niesprawdzonych wyjątków na liście
throws
.
Uwaga: lista throws
jest również używana przez narzędzie javadoc podczas generowania dokumentacji API oraz typowe wskazówki IDE dotyczące „tekstu aktywowanego”.
Rzuty i zastępowanie metod
Klauzula throws
stanowi część podpisu metody w celu zastąpienia metody. Metodę przesłonięcia można zadeklarować z tym samym zestawem sprawdzonych wyjątków, jak zgłoszony przez przesłoniętą metodę lub z podzbiorem. Jednak metoda przesłonięcia nie może dodawać dodatkowych sprawdzonych wyjątków. Na przykład:
@Override
public void checkEven(int number) throws NullPointerException // OK—NullPointerException is an unchecked exception
...
@Override
public void checkEven(Double number) throws OddNumberException // OK—identical to the superclass
...
class PrimeNumberException extends OddNumberException {}
class NonEvenNumberException extends OddNumberException {}
@Override
public void checkEven(int number) throws PrimeNumberException, NonEvenNumberException // OK—these are both subclasses
@Override
public void checkEven(Double number) throws IOExcepion // ERROR
Powodem tej reguły jest to, że jeśli przesłonięta metoda może zgłosić sprawdzony wyjątek, którego przesłonięta metoda nie może zgłosić, spowoduje to przerwanie podstawiania typu.