Exception Java non interceptée?

170

J'ai un petit problème théorique avec les constructions try-catch.

J'ai passé hier un examen pratique sur Java et je ne comprends pas l'exemple suivant:

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 question était "à quoi ressemblera la sortie?"

J'étais à peu près sûr que ce serait AB2C3, MAIS surprise surprise, ce n'est pas vrai.

La bonne réponse est ABC3 (testé et c'est vraiment comme ça).

Ma question est la suivante: où est passée l'exception («2»)?

Kousalik
la source
8
+1 Ahh mec, je connaissais cette réponse. On m'a demandé cela dans une interview. C'est une très bonne question pour comprendre comment try / catch / enfin fonctionne sur la pile.
Mais je ne suis pas une classe de wrapper
10
Il n'y a qu'une seule instruction d'impression qui pourrait imprimer un nombre (le dernier :) print(e.getMessage()). Vous pensiez que la sortie serait AB2C3: pensez-vous que le catchbloc le plus à l'extérieur serait exécuté deux fois?
Adrian Pronk
En java, avant qu'une instruction qui transfère le contrôle hors du bloc catch soit exécutée, le bloc finally est exécuté à condition qu'il existe. Si seul le code du bloc finally ne transfère pas le contrôle vers l'extérieur, l'instruction retardée du bloc catch est exécutée.
Thomas

Réponses:

198

À partir de la spécification du langage Java 14.20.2. :

Si le bloc catch se termine brusquement pour la raison R, alors le bloc finally est exécuté. Ensuite, il y a un choix:

  • Si le bloc finally se termine normalement, alors l'instruction try se termine brusquement pour la raison R.

  • Si le bloc finally se termine brusquement pour la raison S, alors l'instruction try se termine brusquement pour la raison S (et la raison R est rejetée) .

Donc, quand il y a un bloc catch qui lève une exception:

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

mais il y a aussi un bloc finally qui lève également une exception:

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

Exception("2")seront rejetés et seuls Exception("3")seront propagés.

Adam Siemion
la source
72
Cela vaut même pour les returndéclarations. Si votre bloc finally a un retour, il remplacera tout retour dans un bloc tryou catch. En raison de ces "fonctionnalités", une bonne pratique est que le bloc finally ne doit jamais lever d'exception ou avoir une instruction de retour.
Augusto
C'est aussi l'avantage d'héritage que try-with-resources a dans Java 7. Il préserve l'exception initiale si une exception secondaire est générée lors de la fermeture des ressources, ce qui facilite généralement le débogage.
w25r
19

Les exceptions levées dans le bloc finally suppriment l'exception lancée plus tôt dans le bloc try ou catch.

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

De l' exemple de 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();
    }
}

Cependant, dans cet exemple, si les méthodes readLine et close les deux lancent des exceptions, alors la méthode readFirstLineFromFileWithFinallyBlock lève l'exception levée à partir du bloc finally; l'exception lancée depuis le bloc try est supprimée.


La nouvelle try-withsyntaxe de Java 7 ajoute une autre étape de suppression des exceptions: les exceptions levées dans le bloc try suppriment celles lancées plus tôt dans la partie try-with.

du même exemple:

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

Une exception peut être levée à partir du bloc de code associé à l'instruction try-with-resources. Dans l'exemple ci-dessus, une exception peut être levée à partir du bloc try et jusqu'à deux exceptions peuvent être levées à partir de l'instruction try-with-resources lorsqu'il tente de fermer les objets ZipFile et BufferedWriter. Si une exception est levée à partir du bloc try et qu'une ou plusieurs exceptions sont levées à partir de l'instruction try-with-resources, alors ces exceptions levées à partir de l'instruction try-with-resources sont supprimées et l'exception levée par le bloc est celle qui est levée par la méthode writeToFileZipFileContents. Vous pouvez récupérer ces exceptions supprimées en appelant la méthode Throwable.getSuppressed à partir de l'exception levée par le bloc try.


Dans le code à partir de la question, chaque bloc rejette clairement l'ancienne exception, sans même la consigner, ce qui n'est pas bon lorsque vous essayez de résoudre certains bogues:

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

Dakota du Sud
la source
9

Puisqu'il throw new Exception("2");est jeté du catchbloc et non try, il ne sera plus rattrapé.
Voir 14.20.2. Exécution de try-finally et try-catch-finally .

Voici ce qui se passe:

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
la source
oui c'est vrai, je vois que cela se passe, mais je cherchais une explication - pourquoi il se comporte de cette façon
Kousalik
5

Votre question est très évidente, et la réponse est simple dans la même mesure. L'objet Exception avec le message "2" est écrasé par l'objet Exception avec le message comme "3".

Explication: Lorsqu'une exception se produit, son objet est renvoyé pour attraper le bloc à gérer. Mais lorsqu'une exception se produit dans le bloc catch lui-même, son objet est transféré vers OUTER CATCH Block (le cas échéant) pour la gestion des exceptions. Et la même chose s'est produite ici. L'objet d'exception avec le message "2" est transféré vers OUTER catch Block. Mais attendez ... Avant de quitter le bloc try-catch interne, il DOIT EXÉCUTER ENFIN. C'est là que s'est produit le changement qui nous préoccupe. Un nouvel objet EXCEPTION (avec le message "3") est jeté ou ce bloc finally qui a remplacé l'objet Exception déjà lancé (avec le message "2"). En conséquence, lorsque le message de l'objet Exception est imprimé, nous avons obtenu valeur remplacée, c'est-à-dire "3" et non "2".

Gardez à l'esprit: un seul objet d'exception peut être géré par un bloc CATCH.

Bharat
la source
2

Le finallybloc s'exécute toujours. Soit vous returnde l'intérieur du bloc try, soit une exception est levée. L'exception lancée dans le finallybloc remplacera celle lancée dans la branche catch.

De plus, lever une exception ne provoquera aucune sortie en soi. La ligne throw new Exception("2");n'écrira rien.

allprog
la source
1
oui, je sais que lancer Exception ne produit rien par lui-même, mais je n'ai pas vu la raison pour laquelle l'exception 2 devrait être abandonnée. Je suis encore un peu plus intelligent :-)
Kousalik
est toujours très long et dans très longtemps tout peut arriver (voir puzzle wouter.coekaerts.be/2012/puzzle-dreams )
Dainius
0

Selon votre code:

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

Comme vous pouvez le voir ici:

  1. print A et lève une exception # 1;
  2. cette exception a été interceptée par l'instruction catch et print B - # 2;
  3. block # 3s'exécute finalement après l'instruction try-catch (ou seulement try, si aucune exception ne s'est produite) et affiche C - # 4et lève une nouvelle exception;
  4. celui-ci a été capturé par une déclaration catch externe # 5;

Le résultat est ABC3. Et 2est omis de la même manière que1

nazar_art
la source
Désolé, l'exception ("1") n'est pas omise, mais est interceptée avec succès
Black Maggie
@Black Maggie Il est mis en cache et lancé une nouvelle exception => ce n'est pas mis en cache et le programme est terminé. Et avant que ce bloc soit enfin exécuté.
nazar_art