Szukaj…


Wprowadzenie

Obiekty typu 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 po try...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 .
    • Jeśli tak nie jest, oryginalny wyjątek jest nadal propagowany.

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

Java SE 7

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ę klasy IllegalArgumentException 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 jako throws 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:

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

Java SE 7

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ą Autocloseablezdecydowanie 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 and ServerSocket i ich podklasy
  • Channel i jego podklasy, oraz
  • interfejsy JDBC Connection , Statement i ResultSet 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 i finally 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:

  1. 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();
    
  2. 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

Java SE 1.4

Łą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:

  1. Rozróżnienie między sprawdzonymi i niesprawdzonymi wyjątkami opisano poniżej.
  2. Klasa Throwable , Exception i RuntimeException należy traktować jako abstract ; patrz Pitfall - Throwing Throwable, Exception, Error lub RuntimeException .
  3. JVM zgłasza wyjątki dotyczące Error w sytuacjach, w których próba odzyskania aplikacji byłaby niebezpieczna lub nierozsądna.
  4. Rozsądne byłoby deklarowanie niestandardowych podtypów Throwable . Narzędzia i biblioteki Java mogą zakładać, że Error i Exception są jedynymi bezpośrednimi podtypami Throwable 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 klauzuli throws 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:

  1. Rozpocznij wykonywanie kodu w bloku try .
  2. 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ątku ArithmeticException ).
  3. Jeśli wyjątek zostanie przechwycony , zostaje przypisany do zmiennej e i wykonywany jest blok catch .
  4. Jeśli blok try lub catch zostanie zakończony (tzn. Podczas wykonywania kodu nie wystąpią nieprzechwycone wyjątki), kontynuuj wykonywanie kodu poniżej bloku try-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ń

Java SE 1.4

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ń:

  1. Informacje dostępne w StackTraceElement są ograniczone. Nie ma więcej dostępnych informacji, niż wyświetla printStackTrace . (Wartości zmiennych lokalnych w ramce nie są dostępne.)

  2. getStackTrace() dla getStackTrace() 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

Java SE 1.4

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 SE 7

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 .
  • 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 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 blokiem try , aby znalazł się w zakresie dla bloku finally .
  • Umieszczając new FileReader(...) , catch może obsłużyć każdy wyjątek IOError new FileReader(...) podczas otwierania pliku.
  • Potrzebujemy reader.close() w finally bloku, ponieważ istnieją pewne ścieżki wyjątków, których nie możemy przechwycić ani w bloku try , ani w bloku catch .
  • Ponieważ jednak wyjątek mógł zostać zgłoszony przed zainicjowaniem reader , potrzebujemy również jawnego testu null .
  • 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 SE 7

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:

  1. Informuje kompilator, które wyjątki są zgłaszane, aby kompilator mógł zgłaszać nieprzechwycone (zaznaczone) wyjątki jako błędy.

  2. 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.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow