Wyjątek wrzucony do bloku catch - czy zostanie złapany ponownie?


180

Może się to wydawać pytaniem o programowanie 101 i myślałem, że znam odpowiedź, ale teraz muszę sprawdzić dwukrotnie. W tym fragmencie kodu poniżej, czy wyjątek rzucony w pierwszym bloku catch zostanie następnie przechwycony przez ogólny blok Exception catch poniżej?

try {
  // Do something
} catch(IOException e) {
  throw new ApplicationException("Problem connecting to server");
} catch(Exception e) {
  // Will the ApplicationException be caught here?
}

Zawsze myślałem, że odpowiedź brzmi: nie, ale teraz mam dziwne zachowanie, które może być przez to spowodowane. Odpowiedź jest prawdopodobnie taka sama dla większości języków, ale pracuję w Javie.


2
Może mógłbyś opisać „dziwne zachowanie”?
Jeffrey L Whitledge,

Czy na pewno ApplicationException nie jest wyrzucany gdzie indziej i propagowany do tego bloku?
sblundy

Zauważyłem to w moim Eclipse IDE. Zmusza mnie to do umieszczenia „rzutu nowego wyjątku” w bloku prób, ale nie wiem dlaczego. Robiłem to w przeszłości bez tego. Nie rozumiem, dlaczego byłby wymagany blok próbny. Wiele przykładów w Google pokazuje, że ludzie nie potrzebują bloku try. Czy to dlatego, że wrzucam wewnątrz oświadczenie if?
djangofan,

Odpowiedzi:


214

Nie, ponieważ nowy thrownie znajduje się trybezpośrednio w bloku.


Powiedzmy, że jeśli kod jest taki, spróbuj {} catch (Exception e) {System.err.println ("In catch Exception:" + e.getClass ()); } catch (IOException e) {System.err.println ("In catch IOException:" + e.getClass ()); }, a kod w bloku try generuje wyjątek IO, czy przejdzie do bezpośredniego bloku ogólnego wyjątku, czy też przejdzie do bloku IOException catch?
sofs1

4
@ user3705478 Kod nie skompiluje się, aby uniknąć tego rodzaju błędnej sytuacji. Ogólnie rzecz biorąc, nie możesz mieć złapania dla podklasy po złapaniu jej nadklasy w tym samym bloku try.
Chris Jester-Young

Dzięki. Więc otrzymam błąd czasu kompilacji, prawda? Przetestuję to po powrocie do domu.
sofs1

To zależy od @ user3705478, jeśli RuntimeExceptionzostanie wyrzucony z catchbloku, nie wystąpi błąd kompilacji.
Randy the Dev

@AndrewDunn Nie sądzę, aby o to chodziło pytanie użytkownika3705478, ale raczej o to, co się stanie, jeśli catchklauzula wyjątku nadrzędnego zostanie wymieniona przedcatch klauzulą wyjątku podrzędnego . Rozumiem, że Java na to nie zezwala i jest przechwytywana w czasie kompilacji.
Chris Jester-Young

71

Nie. Bardzo łatwo to sprawdzić.

public class Catch {
    public static void main(String[] args) {
        try {
            throw new java.io.IOException();
        } catch (java.io.IOException exc) {
            System.err.println("In catch IOException: "+exc.getClass());
            throw new RuntimeException();
        } catch (Exception exc) {
            System.err.println("In catch Exception: "+exc.getClass());
        } finally {
            System.err.println("In finally");
        }
    }
}

Powinien wydrukować:

W catch IOException: klasa java.io.IOException
W końcu
Wyjątek w wątku „main” java.lang.RuntimeException
        w Catch.main (Catch.java:8)

Z technicznego punktu widzenia mógł to być błąd kompilatora, zależne od implementacji, nieokreślone zachowanie lub coś w tym rodzaju. Jednak JLS jest dość dobrze dopracowany, a kompilatory są wystarczająco dobre do tego rodzaju prostych rzeczy (przypadek narożnika generycznego może być inną sprawą).

Zauważ również, że jeśli zamienisz się dwoma blokami catch, to się nie skompiluje. Drugi haczyk byłby całkowicie nieosiągalny.

Zauważ, że ostatni blok zawsze działa, nawet jeśli wykonywany jest blok catch (poza głupimi przypadkami, takimi jak nieskończone pętle, dołączanie przez interfejs narzędzi i zabijanie wątku, przepisywanie kodu bajtowego itp.).


3
Najbardziej oczywistym sposobem uniknięcia a finallyjest oczywiście sprawdzenie System.exit. :-P
Chris Jester-Young,

3
@Chris Jester-Young for(;;);jest krótszy, zawarty w języku, nie wprowadza zbytnio efektów ubocznych i dla mnie jest bardziej oczywisty.
Tom Hawtin - tackline

1
System.exitjest bardziej przyjazny dla procesora! : -O Ale tak, ok, oczywiście jest to kryterium subiektywne. Nie wiedziałem też, że jesteś golfistą szyfrującym. ;-)
Chris Jester-Young

28

Specyfikacja języka Java mówi w sekcji 14.19.1:

Jeśli wykonanie bloku try kończy się nagle z powodu rzutu o wartości V, wówczas istnieje wybór:

  • Jeśli typ czasu wykonywania V można przypisać do parametru dowolnej klauzuli catch instrukcji try, wówczas wybierana jest pierwsza (najbardziej po lewej) taka klauzula catch. Wartość V jest przypisywana do parametru wybranej klauzuli catch i wykonywany jest blok tej klauzuli catch. Jeśli ten blok kończy się normalnie, instrukcja try kończy normalnie; jeśli ten blok kończy się nagle z jakiegokolwiek powodu, instrukcja try kończy się nagle z tego samego powodu.

Źródła : http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#24134

Innymi słowy, pierwszy obejmujący przechwyt, który może obsłużyć wyjątek, robi, a jeśli wyjątek zostanie wyrzucony z tego przechwytywania, nie jest to zakres żadnego innego przechwytywania dla oryginalnej próby, więc nie będą próbowali go obsłużyć.

Jedną z powiązanych i mylących rzeczy, które należy wiedzieć, jest to, że w strukturze try- [catch] -finally blok final może zgłosić wyjątek, a jeśli tak, każdy wyjątek rzucony przez blok try lub catch zostaje utracony. To może być mylące, gdy zobaczysz to po raz pierwszy.


Chciałem tylko dodać, że od czasu Java 7 można tego uniknąć używając opcji try-with-resources. Następnie, jeśli tryORAZ finallyoba rzutowanie, finallyjest pomijane, ale jest również DODANE do wyjątku od try. Jeśli również catchrzuca, nie masz szczęścia, chyba że sam sobie z tym poradzisz, używając polecenia „addSuppressed” i dodając trywyjątek - wtedy masz wszystkie trzy.
LAFK mówi Przywróć Monikę

6

Jeśli chcesz zgłosić wyjątek z bloku catch, musisz poinformować o tym swoją metodę / klasę / etc. że musi zgłosić wspomniany wyjątek. Tak jak to:

public void doStuff() throws MyException {
    try {
        //Stuff
    } catch(StuffException e) {
        throw new MyException();
    }
}

A teraz twój kompilator nie będzie na ciebie krzyczał :)


4

Nie - jak powiedział Chris Jester-Young, zostanie on wyrzucony do następnego próbnego złapania w hierarchii.


2

Jak wspomniano powyżej ...
dodam, że jeśli masz problem z zobaczeniem, co się dzieje, jeśli nie możesz odtworzyć problemu w debugerze, możesz dodać ślad przed ponownym wyrzuceniem nowego wyjątku (z dobrym starym Systemem .out.println co gorsza, z dobrym systemem dzienników, takim jak log4j).


2

Nie zostanie złapany przez drugi blok zaczepowy. Każdy wyjątek jest przechwytywany tylko wtedy, gdy znajduje się w bloku próbnym. Możesz jednak zagnieżdżać próby (co nie jest ogólnie dobrym pomysłem):

try {
    doSomething();
} catch (IOException) {
   try {
       doSomething();
   } catch (IOException e) {
       throw new ApplicationException("Failed twice at doSomething" +
       e.toString());
   }          
} catch (Exception e) {
}

Napisałem podobny kod. Ale nie jestem przekonany. Chcę wywołać Thread.sleep () w moim bloku catch. Ale Thread.sleep zgłasza sobie InterruptedException. Czy słuszne jest (najlepsza praktyka) zrobienie tego tak, jak pokazałeś w swoim przykładzie?
riroo

1

Nie, ponieważ wszystkie catch odnoszą się do tego samego bloku try, więc rzucanie z wewnątrz bloku catch zostanie złapane przez otaczający blok try (prawdopodobnie w metodzie, która wywołała ten)


-4

Stary post, ale zmienna „e” musi być unikalna:

try {
  // Do something
} catch(IOException ioE) {
  throw new ApplicationException("Problem connecting to server");
} catch(Exception e) {
  // Will the ApplicationException be caught here?
}

5
To powinien być komentarz, a nie odpowiedź. Nic nie stoi na przeszkodzie, aby odpowiedzieć na rzeczywiste pytanie - jest to tylko krytyka kwestii PO.
Derek W
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.