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”)?


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.


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


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
}

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.


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.


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


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