Eccezione Java non rilevata?


170

Ho un piccolo problema teorico con le costruzioni try-catch.

Ieri ho fatto un esame pratico su Java e non capisco il seguente esempio:

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());
}

La domanda era "come sarà l'output?"

Ero abbastanza sicuro che sarebbe stato AB2C3, ma sorpresa a sorpresa, non è vero.

La risposta giusta è ABC3 (testato ed è davvero così).

La mia domanda è: dove è finita l'eccezione ("2")?


8
+1 Ahh amico, conoscevo questa risposta. Mi è stato chiesto questo in un'intervista. È un'ottima domanda per capire come try / catch / infine funziona in pila.
Ma non sono una classe Wrapper il

10
C'è solo una dichiarazione di stampa che potrebbe stampare un numero (l'ultimo:) print(e.getMessage()). Pensavi che l'uscita sarebbe stata AB2C3: pensavi che il catchblocco più esterno sarebbe stato eseguito due volte?
Adrian Pronk,

In java, prima che venga eseguita un'istruzione che trasferisce il controllo dal blocco catch, il blocco finally viene eseguito purché esista. Se solo il codice nel blocco finalmente non trasferisce il controllo all'esterno, viene eseguita l'istruzione ritardata dal blocco catch.
Thomas,

Risposte:


198

Dalle specifiche del linguaggio Java 14.20.2. :

Se il blocco catch si completa improvvisamente per la ragione R, viene eseguito il blocco finally. Quindi c'è una scelta:

  • Se il blocco finally si completa normalmente, l'istruzione try si completa improvvisamente per il motivo R.

  • Se il blocco finally si completa bruscamente per il motivo S, l'istruzione try si completa bruscamente per il motivo S (e il motivo R viene scartato) .

Quindi, quando c'è un blocco catch che genera un'eccezione:

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

ma c'è anche un blocco finally che genera anche un'eccezione:

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

Exception("2")sarà scartato e solo Exception("3")sarà propagato.


72
Questo vale anche per le returndichiarazioni. Se il tuo blocco finalmente ha un ritorno, sovrascriverà qualsiasi ritorno in un blocco tryo catch. A causa di queste "caratteristiche", una buona pratica è che finalmente il blocco non dovrebbe mai generare un'eccezione o avere un'istruzione return.
Augusto,

Questo è anche il vantaggio intrinseco di provare-con-risorse in Java 7. Conserva l'eccezione iniziale se viene generata un'eccezione secondaria quando si chiudono le risorse, in genere facilitando il debug.
w25r,

19

Le eccezioni generate nel blocco infine eliminano l'eccezione generata in precedenza nel blocco try o catch.

Esempio Java 7: http://ideone.com/0YdeZo

Dal di Javadoc esempio:


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

Tuttavia, in questo esempio, se i metodi readLine e chiudono entrambi generano eccezioni, il metodo readFirstLineFromFileWithFinallyBlock genera l'eccezione generata dal blocco finally; l'eccezione generata dal blocco try viene soppressa.


La nuova try-withsintassi di Java 7 aggiunge un altro passaggio di soppressione delle eccezioni: le eccezioni lanciate nel blocco try sopprimono quelle lanciate in precedenza nella parte try-with.

dallo stesso esempio:

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());
        }
    }

È possibile generare un'eccezione dal blocco di codice associato all'istruzione try-with-resources. Nell'esempio precedente, è possibile generare un'eccezione dal blocco try e fino a due eccezioni dall'istruzione try-with-resources quando si tenta di chiudere gli oggetti ZipFile e BufferedWriter. Se viene generata un'eccezione dal blocco try e vengono generate una o più eccezioni dall'istruzione try-with-resources, le eccezioni generate dall'istruzione try-with-resources vengono eliminate e l'eccezione generata dal blocco è quella generato dal metodo writeToFileZipFileContents. È possibile recuperare queste eccezioni soppresse chiamando il metodo Throwable.getSuppressed dall'eccezione generata dal blocco try.


Nel codice in questione, ogni blocco sta chiaramente scartando la vecchia eccezione, nemmeno registrandola, non va bene quando si tenta di risolvere alcuni bug:

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


9

Poiché throw new Exception("2");viene lanciato dal catchblocco e non try, non verrà catturato di nuovo.
Vedi 14.20.2. Esecuzione di try-finally e try-catch-finally .

Questo è ciò che sta accadendo:

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
}

sì, è vero, vedo che sta succedendo, ma stavo cercando una spiegazione - perché si sta comportando in questo modo
Kousalik

5

La tua domanda è molto ovvia e la risposta è semplice nella stessa misura. L'oggetto Exception con messaggio come "2" viene sovrascritto dall'oggetto Exception con messaggio come "3".

Spiegazione: Quando si verifica un'eccezione, l'oggetto viene lanciato per catturare il blocco da gestire. Ma quando si verifica un'eccezione nel blocco catch stesso, il suo oggetto viene trasferito al blocco OUTER CATCH (se presente) per la gestione delle eccezioni. E lo stesso è successo qui. L'oggetto eccezione con il messaggio "2" viene trasferito nel blocco catch OUTER. Ma aspetta .. Prima di lasciare il blocco try-catch interno, DEVE ESSERE ESEGUITO FINALMENTE. Qui è avvenuto il cambiamento di cui siamo preoccupati. Viene lanciato un nuovo oggetto EXCEPTION (con il messaggio "3") o questo blocco infine che ha sostituito l'oggetto eccezione già lanciato (con il messaggio "2"). Di conseguenza, quando viene stampato il messaggio dell'oggetto Exception, si ottiene valore ignorato, ad esempio "3" e non "2".

Ricorda: solo un oggetto eccezione può essere gestito dal blocco CATCH.


2

Il finallyblocco funziona sempre. O returndall'interno del blocco try o viene generata un'eccezione. L'eccezione generata nel finallyblocco sovrascriverà quella generata nel ramo di cattura.

Inoltre, generare un'eccezione non causerà alcun output da solo. La riga throw new Exception("2");non scriverà nulla.


1
sì, so di non aver prodotto l'eccezione in uscita da solo, ma non ho visto la ragione, perché l'eccezione 2 dovrebbe essere eliminata. Sono di nuovo un po 'più intelligente :-)
Kousalik il

è sempre molto tempo e molto tempo può succedere di tutto (controlla puzzle wouter.coekaerts.be/2012/puzzle-dreams )
Dainius

0

Secondo il tuo codice:

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());
}

Come puoi vedere qui:

  1. stampa A e genera un'eccezione # 1;
  2. questa eccezione è stata catturata dalla dichiarazione di cattura e dalla stampa B - # 2;
  3. il blocco # 3viene infine eseguito dopo l'istruzione try-catch (o solo try, se non si fosse verificata alcuna eccezione) e stampa C - # 4e genera una nuova eccezione;
  4. questo è stato catturato da una dichiarazione di cattura esterna # 5;

Il risultato è ABC3. E 2viene omesso allo stesso modo di1


Siamo spiacenti, l'eccezione ("1") non viene omessa, ma viene rilevata correttamente
Black Maggie

@ Black Maggie Viene memorizzato nella cache e viene generata una nuova eccezione => questo non viene memorizzato nella cache e il programma è terminato. E prima che questo blocco sia finalmente eseguito.
nazar_art,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.