Wyjątek Java nie został przechwycony?

170

Mam mały problem teoretyczny z konstrukcjami typu try-catch.

Wczoraj zdałem praktyczny egzamin z języka Java i nie rozumiem następującego przykładu:

try {
    try {
        System.out.print("A");
        throw new Exception("1");
    } catch (Exception e) {
        System.out.print("B");
        throw new Exception("2");
    } finally {
        System.out.print("C");
        throw new Exception("3");
    }
} catch (Exception e) {
    System.out.print(e.getMessage());
}

Pytanie brzmiało "jak będzie wyglądał wynik?"

Byłem prawie pewien, że to będzie AB2C3, ALE niespodzianka, to nieprawda.

Prawidłowa odpowiedź to ABC3 (przetestowane i naprawdę tak jest).

Moje pytanie brzmi: gdzie poszedł wyjątek („2”)?

Kousalik
źródło
8
+1 Ahh stary, znałem tę odpowiedź. Zapytano mnie o to w wywiadzie. To bardzo dobre pytanie, aby zrozumieć, jak try / catch / w końcu działa na stosie.
Ale nie jestem klasą
10
Jest tylko jedna instrukcja print, która może wypisać liczbę (ostatnia print(e.getMessage()):). Pomyślałeś, że wynik będzie następujący AB2C3: czy sądzisz, że najbardziej zewnętrzny catchblok zostanie wykonany dwukrotnie?
Adrian Pronk
W Javie, przed wykonaniem instrukcji przekazującej sterowanie z bloku catch, wykonywany jest ostatni blok, o ile istnieje. Jeśli tylko kod z bloku last nie przekazuje kontroli na zewnątrz, zostanie wykonana opóźniona instrukcja z bloku catch.
Thomas

Odpowiedzi:

198

Ze specyfikacji języka Java 14.20.2. :

Jeśli blok catch kończy się nagle z powodu R, wykonywany jest ostatni blok. Następnie jest wybór:

  • Jeśli ostatni blok kończy się normalnie, instrukcja try kończy się nagle z powodu R.

  • Jeśli ostatni blok kończy się nagle z powodu S, to instrukcja try kończy się nagle z powodu S (i powód R jest odrzucany) .

Tak więc, gdy istnieje blok catch, który zgłasza wyjątek:

try {
    // ...
} catch (Exception e) {
    throw new Exception("2");
}

ale jest też ostatni blok, który również zgłasza wyjątek:

} finally {
    throw new Exception("3");
}

Exception("2")zostaną odrzucone i tylko Exception("3")będą propagowane.

Adam Siemion
źródło
72
Dotyczy to nawet returnstwierdzeń. Jeśli twój ostatni blok ma zwrot, zastąpi on każdy powrót w bloku trylub catch. Ze względu na te „funkcje” dobrą praktyką jest to, aby ostatecznie blok nigdy nie zgłaszał wyjątku ani nie zawierał instrukcji return.
Augusto
Jest to również zaleta dziedziczenia, jaką try-with-resources ma w Javie 7. Zachowuje początkowy wyjątek, jeśli podczas zamykania zasobów generowany jest dodatkowy wyjątek, co zazwyczaj ułatwia debugowanie.
w25r
19

Wyjątki zgłoszone w final block pomijają wyjątek zgłoszony wcześniej w bloku try lub catch.

Przykład Java 7: http://ideone.com/0YdeZo

Z przykładu Javadoc :


static String readFirstLineFromFileWithFinallyBlock(String path)
                                                     throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        if (br != null) br.close();
    }
}

Jednak w tym przykładzie, jeśli metody readLine i zamkną oba wyjątki rzutów, metoda readFirstLineFromFileWithFinallyBlock zgłosi wyjątek zgłoszony z ostatniego bloku; wyjątek zgłoszony z bloku try jest pomijany.


Nowa try-withskładnia Java 7 dodaje kolejny krok do pomijania wyjątków: wyjątki zgłoszone w bloku try pomijają te, które zostały wcześniej zgłoszone w części próbnej.

z tego samego przykładu:

try (
        java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName);
        java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
    ) {
        for (java.util.Enumeration entries = zf.entries(); entries.hasMoreElements();) {
            String newLine = System.getProperty("line.separator");
            String zipEntryName = ((java.util.zip.ZipEntry)entries.nextElement()).getName() + newLine;
            writer.write(zipEntryName, 0, zipEntryName.length());
        }
    }

Wyjątek może zostać zgłoszony z bloku kodu skojarzonego z instrukcją try-with-resources. W powyższym przykładzie wyjątek może zostać zgłoszony z bloku try, a przy próbie zamknięcia obiektów ZipFile i BufferedWriter z instrukcji try-with-resources można wyrzucić maksymalnie dwa wyjątki. Jeśli wyjątek zostanie zgłoszony z bloku try i jeden lub więcej wyjątków zostanie wyrzuconych z instrukcji try-with-resources, to te wyjątki wyrzucone z instrukcji try-with-resources zostaną pominięte, a wyjątek zgłoszony przez blok to ten który jest generowany przez metodę writeToFileZipFileContents. Te pominięte wyjątki można pobrać, wywołując metodę Throwable.getSuppressed z wyjątku zgłoszonego przez blok try.


W kodzie z pytania każdy blok po prostu odrzuca stary wyjątek, nawet go nie rejestruje, co nie jest dobre, gdy próbujesz rozwiązać niektóre błędy:

http://en.wikipedia.org/wiki/Error_hiding

SD
źródło
9

Ponieważ throw new Exception("2");jest wyrzucany z catchbloku, a nie try, nie zostanie ponownie złapany.
Patrz 14.20.2. Wykonywanie prób na koniec i wreszcie próba złapania .

Oto, co się dzieje:

try {
    try {
        System.out.print("A");         //Prints A
        throw new Exception("1");   
    } catch (Exception e) { 
        System.out.print("B");         //Caught from inner try, prints B
        throw new Exception("2");   
    } finally {
        System.out.print("C");         //Prints C (finally is always executed)
        throw new Exception("3");  
    }
} catch (Exception e) {
    System.out.print(e.getMessage());  //Prints 3 since see (very detailed) link
}
Maroun
źródło
tak, zgadza się, widzę, że to się dzieje, ale szukałem wyjaśnienia - dlaczego tak się zachowuje
Kousalik
5

Twoje pytanie jest bardzo oczywiste, a odpowiedź równie prosta. Obiekt Exception z komunikatem „2” jest nadpisywany przez obiekt Exception z komunikatem „3”.

Objaśnienie: Gdy wystąpi wyjątek, jego obiekt jest rzucany w celu złapania bloku do obsłużenia. Ale gdy wyjątek wystąpi w samym bloku catch, jego obiekt jest przenoszony do bloku OUTER CATCH (jeśli istnieje) w celu obsługi wyjątków. I to samo stało się tutaj. Obiekt wyjątku z komunikatem „2” jest przesyłany do bloku OUTER catch. Ale czekaj … Przed opuszczeniem wewnętrznego bloku try-catch MUSI WYKONAĆ OSTATECZNIE. Tutaj nastąpiła zmiana, o którą się martwimy. Zostaje wyrzucony nowy obiekt EXCEPTION (z komunikatem „3”) lub ten ostatecznie blok, który zastąpił już wrzucony obiekt Exception (z komunikatem „2”). W wyniku czego podczas drukowania komunikatu obiektu Exception otrzymaliśmy nadpisana wartość, tj. „3” a nie „2”.

Keep Remember: Tylko jeden obiekt wyjątku może być obsługiwany przez blok CATCH.

Bharat
źródło
2

finallyBlok zawsze działa. Albo jesteś returnz wewnątrz bloku try, albo wyrzucany jest wyjątek. Wyjątek zgłoszony w finallybloku zastąpi wyjątek zgłoszony w gałęzi catch.

Ponadto zgłoszenie wyjątku samo w sobie nie spowoduje żadnego wyniku. Linia throw new Exception("2");nic nie wypisze.

allprog
źródło
1
tak, wiem, że wyrzucanie wyjścia wyjątku nie jest samoistne, ale nie widziałem powodu, dla którego powinien zostać usunięty wyjątek 2. Znowu jestem trochę mądrzejszy :-)
Kousalik
zawsze jest bardzo długi czas i za bardzo długi czas wszystko może się zdarzyć (sprawdź puzzle wouter.coekaerts.be/2012/puzzle-dreams )
Dainius
0

Według twojego kodu:

try {
    try {
        System.out.print("A");
        throw new Exception("1");   // 1
    } catch (Exception e) {
        System.out.print("B");      // 2
        throw new Exception("2");
    } finally {                     // 3
        System.out.print("C");      // 4 
        throw new Exception("3");
    }
} catch (Exception e) {             // 5
    System.out.print(e.getMessage());
}

Jak widać tutaj:

  1. wypisuje A i zgłasza wyjątek # 1;
  2. ten wyjątek został przechwycony przez instrukcję catch i print B - # 2;
  3. blok w końcu jest # 3wykonywany po instrukcji try-catch (lub tylko try, jeśli nie wystąpił żaden wyjątek) i wyświetla C - # 4i rzuca nowy wyjątek;
  4. ten został złapany przez zewnętrzną instrukcję połowową # 5;

Wynik jest ABC3. I 2jest pomijany w taki sam sposób jak1

nazar_art
źródło
Przepraszamy, wyjątek („1”) nie został pominięty, ale został pomyślnie przechwycony
Black Maggie
@Black Maggie Jest zapisany w pamięci podręcznej i zgłoszony nowy wyjątek => to nie jest buforowane i program został zakończony. I zanim ten blok w końcu zostanie wykonany.
nazar_art