Recherche…


Introduction

Les objets de type Throwable et ses sous-types peuvent être envoyés à la pile avec le mot-clé throw et capturés avec try…catch instructions.

Syntaxe

  • void someMethod () lève SomeException {} // déclaration de méthode, force les appelants de méthode à intercepter si SomeException est un type d'exception vérifié

  • essayez {

    someMethod(); //code that might throw an exception 
    

    }

  • catch (SomeException e) {

     System.out.println("SomeException was thrown!"); //code that will run if certain exception (SomeException) is thrown
    

    }

  • enfin {

     //code that will always run, whether try block finishes or not
    

    }

Prendre une exception avec try-catch

Une exception peut être interceptée et gérée à l'aide de l'instruction try...catch . (En fait, les instructions try prennent d'autres formes, comme décrit dans d'autres exemples sur try...catch...finally et try-with-resources .)

Attrapez avec un bloc catch

La forme la plus simple ressemble à ceci:

try {
    doSomething();
} catch (SomeException e) {
    handle(e);
}
// next statement

Le comportement d'une simple try...catch est le suivant:

  • Les instructions du bloc try sont exécutées.
  • Si aucune exception n'est levée par les instructions du bloc try , le contrôle passe à l'instruction suivante après le try...catch .
  • Si une exception est lancée dans le bloc try .
    • L'objet exception est testé pour voir s'il s'agit d'une instance de SomeException ou d'un sous-type.
    • S'il est, le catch bloc va attraper l'exception:
      • La variable e est liée à l'objet exception.
      • Le code dans le bloc catch est exécuté.
      • Si ce code renvoie une exception, l'exception nouvellement renvoyée est propagée à la place de celle d'origine.
      • Sinon, le contrôle passe à l'instruction suivante après le try...catch .
    • Si ce n'est pas le cas, l'exception d'origine continue à se propager.

Prise d'essai avec plusieurs prises

Un try...catch peut aussi avoir plusieurs blocs de catch . Par exemple:

try {
    doSomething();
} catch (SomeException e) {
    handleOneWay(e)
} catch (SomeOtherException e) {
    handleAnotherWay(e);
}
// next statement

S'il y a plusieurs blocs catch , ils sont essayés un à la fois en commençant par le premier, jusqu'à ce qu'une correspondance soit trouvée pour l'exception. Le gestionnaire correspondant est exécuté (comme ci-dessus), puis le contrôle est transmis à l'instruction suivante après l'instruction try...catch . Les blocs catch après celui qui correspond sont toujours ignorés, même si le code du gestionnaire renvoie une exception .

La stratégie d'appariement «descendante» a des conséquences dans les cas où les exceptions dans les blocs de catch ne sont pas disjointes. Par exemple:

try {
    throw new RuntimeException("test");
} catch (Exception e) {
    System.out.println("Exception");
} catch (RuntimeException e) {
    System.out.println("RuntimeException");
}

Cet extrait de code affichera "Exception" plutôt que "RuntimeException". Puisque RuntimeException est un sous-type d' Exception , le premier catch (plus général) sera mis en correspondance. La seconde catch (plus spécifique) ne sera jamais exécutée.

La leçon à en tirer est que les blocs de catch les plus spécifiques (en termes de types d'exception) devraient apparaître en premier et les plus généraux en dernier. (Certains compilateurs Java vous avertiront si un catch ne peut jamais être exécuté, mais ce n’est pas une erreur de compilation.)

Blocs d'interception multi-exception

Java SE 7

À partir de Java SE 7, un bloc catch unique peut gérer une liste d'exceptions non associées. Le type d'exception est répertorié, séparé par un symbole de barre verticale ( | ). Par exemple:

try {
    doSomething();
} catch (SomeException | SomeOtherException e) {
    handleSomeException(e);
} 

Le comportement d'une capture multi-exception est une simple extension pour le cas à exception unique. Le catch correspond si l'exception levée correspond (au moins) à l'une des exceptions répertoriées.

Il y a une subtilité supplémentaire dans la spécification. Le type de e est une union synthétique des types d'exception de la liste. Lorsque la valeur de e est utilisée, son type statique est le supertype le moins commun de la union de type. Toutefois, si e est renvoyé dans le bloc catch , les types d'exception levés sont les types de l'union. Par exemple:

public void method() throws IOException, SQLException
    try {
        doSomething();
    } catch (IOException | SQLException e) {
        report(e);
        throw e;
    }

Dans ce qui précède, IOException et SQLException sont des exceptions vérifiées dont le supertype le moins commun est Exception . Cela signifie que la méthode de report doit correspondre au report(Exception) . Cependant, le compilateur sait que le throw ne peut lancer qu’une SQLException IOException ou une SQLException . Ainsi, la method peut être déclarée en tant que throws IOException, SQLException plutôt que throws Exception . (Ce qui est une bonne chose: voir Pitfall - Throwable, Exception, Error ou RuntimeException .)

Lancer une exception

L'exemple suivant montre les bases de la création d'une exception:

public void checkNumber(int number) throws IllegalArgumentException {
    if (number < 0) {
        throw new IllegalArgumentException("Number must be positive: " + number);
    }
}

L'exception est lancée sur la 3ème ligne. Cette déclaration peut être divisée en deux parties:

  • new IllegalArgumentException(...) crée une instance de la classe IllegalArgumentException , avec un message décrivant l'erreur IllegalArgumentException par une exception.

  • throw ... lance ensuite l'objet exception.

Lorsque l'exception est levée, les instructions englobantes se terminent anormalement jusqu'à ce que l'exception soit gérée . Ceci est décrit dans d'autres exemples.

Il est recommandé de créer et de lancer l’objet exception dans une seule instruction, comme indiqué ci-dessus. Il est également recommandé d'inclure un message d'erreur significatif dans l'exception pour aider le programmeur à comprendre la cause du problème. Cependant, ce n'est pas nécessairement le message que vous devez montrer à l'utilisateur final. (Pour commencer, Java ne prend pas en charge directement l’internationalisation des messages d’exception.)

Il y a encore quelques points à faire:

  • Nous avons déclaré le checkNumber comme throws IllegalArgumentException . Ce n'était pas strictement nécessaire, car IllegalArgumentException est une exception vérifiée; voir Hiérarchie des exceptions Java - Exceptions non vérifiées et vérifiées . Toutefois, il est conseillé de le faire et d'inclure les exceptions renvoyant les commentaires javadoc d'une méthode.

  • Code immédiatement après une instruction de throw est inaccessible . D'où si nous avons écrit ceci:

     throw new IllegalArgumentException("it is bad");
     return;
    

    le compilateur signalerait une erreur de compilation pour l'instruction return .

Chaîne d'exception

De nombreuses exceptions standard ont un constructeur avec un deuxième argument de cause en plus de l'argument de message conventionnel. La cause vous permet d'enchaîner les exceptions. Voici un exemple.

Tout d'abord, nous définissons une exception non vérifiée que notre application va lancer lorsqu'elle rencontre une erreur non récupérable. Notez que nous avons inclus un constructeur qui accepte un argument de cause .

    public class AppErrorException extends RuntimeException {
        public AppErrorException() {
            super();
        }

        public AppErrorException(String message) {
            super(message);
        }

        public AppErrorException(String message, Throwable cause) {
            super(message, cause);
        }
    }

Ensuite, voici un code qui illustre le chaînage des exceptions.

    public String readFirstLine(String file) throws AppErrorException {
        try (Reader r = new BufferedReader(new FileReader(file))) {
            String line = r.readLine();
            if (line != null) {
                return line;
            } else {
                throw new AppErrorException("File is empty: " + file);
            }
        } catch (IOException ex) {
            throw new AppErrorException("Cannot read file: " + file, ex);
        }
    }

Le throw dans le bloc try détecte un problème et le signale via une exception avec un message simple. En revanche, le throw dans le bloc catch gère l'exception IOException l' IOException dans une nouvelle exception (cochée). Cependant, il ne rejette pas l'exception d'origine. En faisant passer l’ IOException tant que cause , nous l’enregistrons pour qu’elle puisse être imprimée dans la trace de la pile, comme expliqué dans la section Création et lecture de chaînes de caractères .

Exceptions personnalisées

Dans la plupart des cas, il est plus simple du point de vue de la conception de code d'utiliser des classes d' Exception génériques existantes lors du lancement d'exceptions. Cela est particulièrement vrai si vous avez uniquement besoin de l'exception pour transmettre un message d'erreur simple. Dans ce cas, RuntimeException est généralement préférable car il ne s'agit pas d'une exception vérifiée. D'autres classes d'exception existent pour les classes d'erreurs courantes:

Les cas où vous ne souhaitez utiliser une classe d'exception personnalisée sont les suivantes:

  • Vous écrivez une API ou une bibliothèque à utiliser par d'autres utilisateurs et vous souhaitez autoriser les utilisateurs de votre API à intercepter et gérer spécifiquement les exceptions de votre API et à les différencier des autres exceptions plus génériques .
  • Vous lancez des exceptions pour un type d'erreur spécifique dans une partie de votre programme, que vous souhaitez intercepter et gérer dans une autre partie de votre programme, et que vous souhaitez pouvoir différencier ces erreurs des autres erreurs plus génériques.

Vous pouvez créer vos propres exceptions personnalisées en étendant RuntimeException pour une exception non contrôlée ou en vérifiant l'exception en étendant toute Exception qui n'est pas également la sous-classe de RuntimeException , car:

Les sous-classes d'exception qui ne sont pas également des sous-classes de RuntimeException sont des exceptions vérifiées

public class StringTooLongException extends RuntimeException {
    // Exceptions can have methods and fields like other classes
    // those can be useful to communicate information to pieces of code catching
    // such an exception
    public final String value;
    public final int maximumLength;

    public StringTooLongException(String value, int maximumLength){
        super(String.format("String exceeds maximum Length of %s: %s", maximumLength, value));
        this.value = value;
        this.maximumLength = maximumLength;
    }
}

Ceux-ci peuvent être utilisés comme exceptions prédéfinies:

void validateString(String value){
    if (value.length() > 30){
        throw new StringTooLongException(value, 30);
    }
}

Et les champs peuvent être utilisés lorsque l'exception est interceptée et gérée:

void anotherMethod(String value){
    try {
        validateString(value);
    } catch(StringTooLongException e){
        System.out.println("The string '" + e.value + 
                "' was longer than the max of " + e.maximumLength );
    }
}

Gardez à l'esprit que, selon la documentation Java d' Oracle :

[...] Si l'on peut raisonnablement s'attendre à ce qu'un client récupère d'une exception, faites-en une exception cochée. Si un client ne peut rien faire pour récupérer à partir de l'exception, faites-en une exception non contrôlée.

Plus:

La déclaration d'essayer avec les ressources

Java SE 7

Comme l'illustre l'exemple de l' instruction try-catch-final , le nettoyage des ressources à l'aide d'une clause finally nécessite une quantité importante de code "chaud-plat" pour implémenter correctement les bordures. Java 7 fournit un moyen beaucoup plus simple de résoudre ce problème sous la forme de l'instruction try-with-resources .

Qu'est ce qu'une ressource?

Java 7 a introduit l'interface java.lang.AutoCloseable pour permettre la gestion des classes à l'aide de l'instruction try-with-resources . Les instances de classes qui implémentent AutoCloseable sont appelées ressources . Celles-ci doivent généralement être éliminées en temps opportun plutôt que de compter sur le ramasse-miettes pour en disposer.

L'interface AutoCloseable définit une méthode unique:

public void close() throws Exception

Une méthode close() doit éliminer la ressource de manière appropriée. La spécification indique qu'il est prudent d'appeler la méthode sur une ressource déjà supprimée. De plus, les classes qui implémentent Autocloseable sont fortement encouragées à déclarer la méthode close() pour générer une exception plus spécifique que Exception , voire aucune exception.

Un large éventail d'interfaces et de classes Java standard implémentent AutoCloseable . Ceux-ci inclus:

  • InputStream , OutputStream et leurs sous-classes
  • Reader , Writer et leurs sous-classes
  • Socket et ServerSocket et leurs sous-classes
  • Channel et ses sous-classes, et
  • les interfaces JDBC Connection , Statement et ResultSet et leurs sous-classes.

Les classes d'application et tierces peuvent également le faire.

L'énoncé de base de try-with-resource

La syntaxe d'un try-with-resources est basée sur des formes classiques de try-catch , try-finally et try-catch-finally . Voici un exemple de formulaire "de base"; c'est à dire la forme sans catch ou finally .

try (PrintStream stream = new PrintStream("hello.txt")) {
    stream.println("Hello world!");
}

Les ressources à gérer sont déclarées comme variables dans la section (...) après la clause try . Dans l'exemple ci-dessus, nous déclarons un stream variable de ressource et l'initialisons à un nouveau PrintStream .

Une fois les variables de ressource initialisées, le bloc try est exécuté. Lorsque cela est terminé, stream.close() sera appelé automatiquement pour garantir que la ressource ne fuit pas. Notez que l'appel close() se produit peu importe la manière dont le bloc se termine.

Les instructions try-with-resource améliorées

L'instruction try-with-resources peut être améliorée avec catch blocs catch et finally , comme avec la syntaxe try-catch-finally pré-Java 7. L'extrait de code suivant ajoute un bloc catch à notre précédent pour gérer l' FileNotFoundException que le constructeur PrintStream peut lancer:

try (PrintStream stream = new PrintStream("hello.txt")) {
    stream.println("Hello world!");
} catch (FileNotFoundException ex) {
    System.err.println("Cannot open the file");
} finally {
    System.err.println("All done");
}

Si l'initialisation de la ressource ou le bloc try lève l'exception, le bloc catch sera exécuté. Le bloc finally sera toujours exécuté, comme avec une instruction try-catch-finally classique.

Il y a quelques choses à noter cependant:

  • La variable de ressource est hors de portée dans les blocs catch et finally .
  • Le nettoyage des ressources se produira avant que l'instruction tente de correspondre au bloc catch .
  • Si le nettoyage automatique des ressources a déclenché une exception, cela peut se produire dans l'un des blocs catch .

Gestion de plusieurs ressources

Les extraits de code ci-dessus montrent une seule ressource en cours de gestion. En fait, try-with-resources peut gérer plusieurs ressources dans une seule déclaration. Par exemple:

try (InputStream is = new FileInputStream(file1);
     OutputStream os = new FileOutputStream(file2)) {
    // Copy 'is' to 'os'
}

Cela se comporte comme prévu. Les deux is et os sont fermés automatiquement à la fin de l' try bloc. Il y a quelques points à noter:

  • Les initialisations se produisent dans l'ordre du code, et les initialiseurs de variables de ressource ultérieurs peuvent utiliser les valeurs des précédentes.
  • Toutes les variables de ressources initialisées avec succès seront nettoyées.
  • Les variables de ressource sont nettoyées dans l'ordre inverse de leurs déclarations.

Ainsi, dans l'exemple ci - dessus, is est initialisé avant os et nettoyé après, et is sera nettoyé s'il y a une exception lors de l' initialisation os .

Équivalence d'essais avec ressources et d'essais classiques

La spécification de langage Java spécifie le comportement des formulaires try-with-resource en fonction de l'instruction try-catch-finally classique. (Veuillez vous référer au JLS pour plus de détails.)

Par exemple, ce try-with-resource de base :

try (PrintStream stream = new PrintStream("hello.txt")) {
    stream.println("Hello world!");
}

est défini pour être équivalent à ce try-catch-finally :

// Note that the constructor is not part of the try-catch statement
PrintStream stream = new PrintStream("hello.txt");

// This variable is used to keep track of the primary exception thrown
// in the try statement. If an exception is thrown in the try block,
// any exception thrown by AutoCloseable.close() will be suppressed.
Throwable primaryException = null;

// The actual try block
try {
    stream.println("Hello world!");
} catch (Throwable t) {
    // If an exception is thrown, remember it for the finally block
    primaryException = t;
    throw t;
} finally {
    if (primaryException == null) {
        // If no exception was thrown so far, exceptions thrown in close() will
        // not be caught and therefore be passed on to the enclosing code.
        stream.close();
    } else {
        // If an exception has already been thrown, any exception thrown in
        // close() will be suppressed as it is likely to be related to the
        // previous exception. The suppressed exception can be retrieved
        // using primaryException.getSuppressed().
        try {
            stream.close();
        } catch (Throwable suppressedException) {
            primaryException.addSuppressed(suppressedException);
        }
    }
}

(Le JLS spécifie que les variables t et primaryException seront invisibles pour le code Java normal.)

La forme améliorée de try-with-resources est spécifiée comme une équivalence avec la forme de base. Par exemple:

try (PrintStream stream = new PrintStream(fileName)) {
    stream.println("Hello world!");
} catch (NullPointerException ex) {
    System.err.println("Null filename");
} finally {
    System.err.println("All done");    
}

est équivalent à:

try {
    try (PrintStream stream = new PrintStream(fileName)) {
        stream.println("Hello world!");
    }
} catch (NullPointerException ex) {
    System.err.println("Null filename");
} finally {
    System.err.println("All done");    
}    

Création et lecture de stacktraces

Lorsqu'un objet d'exception est créée ( par exemple lorsque vous new il), le Throwable constructeur capture d' informations sur le contexte dans lequel l'exception a été créé. Par la suite, ces informations peuvent être générées sous la forme d'un stacktrace, qui peut être utilisé pour diagnostiquer le problème à l'origine de l'exception.

Impression d'un stacktrace

Imprimer une stacktrace consiste simplement à appeler la méthode printStackTrace() . Par exemple:

try {
    int a = 0;
    int b = 0;
    int c = a / b;
} catch (ArithmeticException ex) {
    // This prints the stacktrace to standard output
    ex.printStackTrace();
}

La méthode printStackTrace() sans arguments imprimera sur la sortie standard de l'application; c'est-à-dire le System.out actuel. Il existe également des printStackTrace(PrintStream) et des printStackTrace(PrintWriter) qui impriment sur un Stream ou un Writer spécifié.

Remarques:

  1. Le stacktrace n'inclut pas les détails de l'exception elle-même. Vous pouvez utiliser la toString() pour obtenir ces détails. par exemple

       // Print exception and stacktrace
       System.out.println(ex);
       ex.printStackTrace();
    
  2. L'impression Stacktrace doit être utilisée avec parcimonie. voir Piège - Piles superflues ou inappropriées . Il est souvent préférable d'utiliser une infrastructure de journalisation et de passer l'objet exception à journaliser.

Comprendre un stacktrace

Considérons le programme simple suivant composé de deux classes dans deux fichiers. (Nous avons montré les noms de fichiers et les numéros de ligne ajoutés à des fins d’illustration.)

File: "Main.java"
1   public class Main {
2       public static void main(String[] args) {
3           new Test().foo();
4       }
5   }

File: "Test.java"
1   class Test {
2       public void foo() {
3           bar();
4       }
5   
6       public int bar() {
7           int a = 1;
8           int b = 0;
9           return a / b;
10      }

Lorsque ces fichiers sont compilés et exécutés, nous obtiendrons la sortie suivante.

Exception in thread "main" java.lang.ArithmeticException: / by zero
        at Test.bar(Test.java:9)
        at Test.foo(Test.java:3)
        at Main.main(Main.java:3)

Lisons cette ligne à la fois pour comprendre ce qu’elle nous dit.

La ligne n ° 1 nous indique que le thread appelé "main" est terminé en raison d'une exception non interceptée. Le nom complet de l'exception est java.lang.ArithmeticException et le message d'exception est "/ by zero".

Si nous recherchons les javadocs pour cette exception, cela dit:

Lancé lorsqu'une condition arithmétique exceptionnelle s'est produite. Par exemple, un entier "diviser par zéro" lève une instance de cette classe.

En effet, le message "/ by zero" est un indice fort que la cause de l'exception est qu'un code a tenté de diviser quelque chose par zéro. Mais quoi?

Les 3 lignes restantes sont la trace de la pile. Chaque ligne représente un appel de méthode (ou de constructeur) sur la pile d'appels, et chacune nous indique trois choses:

  • le nom de la classe et de la méthode en cours d'exécution,
  • le nom de fichier du code source,
  • le numéro de ligne du code source de l'instruction en cours d'exécution

Ces lignes d'un stacktrace sont répertoriées avec le cadre de l'appel en cours. Le cadre supérieur de notre exemple ci-dessus se trouve dans la méthode Test.bar et à la ligne 9 du fichier Test.java. C'est la ligne suivante:

    return a / b;

Si nous examinons quelques lignes plus tôt dans le fichier où b est initialisé, il est évident que b aura la valeur zéro. Nous pouvons dire sans aucun doute que c'est la cause de l'exception.

Si nous devions aller plus loin, nous pouvons voir à partir du stacktrace que bar() été appelé depuis foo() à la ligne 3 de Test.java, et que foo() été à son tour appelé depuis Main.main() .

Remarque: Les noms de classe et de méthode dans les cadres de pile sont les noms internes des classes et des méthodes. Vous devrez reconnaître les cas inhabituels suivants:

  • Une classe imbriquée ou interne ressemblera à "OuterClass $ InnerClass".
  • Une classe interne anonyme ressemblera à "OuterClass $ 1", "OuterClass $ 2", etc.
  • Lorsque du code dans un constructeur, un initialiseur de champ d'instance ou un bloc d'initialisation d'instance est en cours d'exécution, le nom de la méthode sera "".
  • Lorsque le code d'un initialiseur de champ statique ou d'un bloc d'initialisation statique est en cours d'exécution, le nom de la méthode sera "".

(Dans certaines versions de Java, le code de formatage stacktrace détectera et éliminera les séquences répétées de pile, comme cela peut se produire lorsqu'une application échoue en raison d'une récursivité excessive.)

Chaînage des exceptions et empilements imbriqués

Java SE 1.4

Le chaînage des exceptions se produit lorsqu'un morceau de code intercepte une exception, puis en crée et en lance une nouvelle, en faisant passer la première exception comme cause. Voici un exemple:

File: Test,java
1   public class Test {
2      int foo() {
3           return 0 / 0;
4      }
5
6       public Test() {
7           try {
8               foo();
9           } catch (ArithmeticException ex) {
10              throw new RuntimeException("A bad thing happened", ex);
11          }
12      }
13
14      public static void main(String[] args) {
15          new Test();
16      }
17  }

Lorsque la classe ci-dessus est compilée et exécutée, nous obtenons le stacktrace suivant:

Exception in thread "main" java.lang.RuntimeException: A bad thing happened
        at Test.<init>(Test.java:10)
        at Test.main(Test.java:15)
Caused by: java.lang.ArithmeticException: / by zero
        at Test.foo(Test.java:3)
        at Test.<init>(Test.java:8)
        ... 1 more

Le stacktrace commence par le nom de la classe, la méthode et la pile d'appels correspondant à l'exception qui (dans ce cas) a provoqué le blocage de l'application. Ceci est suivi par une ligne "Caused by:" qui signale l'exception de cause . Le nom de la classe et le message sont signalés, suivis des cadres de pile de l'exception cause. La trace se termine par un "... N more" qui indique que les N dernières images sont les mêmes que pour l'exception précédente.

Le "Causé par:" est uniquement inclus dans la sortie lorsque la cause l'exception principale n'est pas null . Les exceptions peuvent être chaînées indéfiniment et, dans ce cas, le stacktrace peut avoir plusieurs traces "Caused by:".

Remarque: le mécanisme de cause était uniquement exposé dans l'API Throwable de Java 1.4.0. Avant cela, le chaînage des exceptions devait être implémenté par l'application en utilisant un champ d'exception personnalisé pour représenter la cause et une méthode printStackTrace personnalisée.

Capturer une stacktrace en tant que chaîne

Parfois, une application doit être capable de capturer un stacktrace en tant que String Java, afin de pouvoir l'utiliser à d'autres fins. L'approche générale pour ce faire consiste à créer un OutputStream ou un Writer temporaire qui écrit dans un tampon en mémoire et le transmet à printStackTrace(...) .

Les bibliothèques Apache Commons et Guava fournissent des méthodes utilitaires pour capturer une stacktrace en tant que chaîne:

org.apache.commons.lang.exception.ExceptionUtils.getStackTrace(Throwable)

com.google.common.base.Throwables.getStackTraceAsString(Throwable)

Si vous ne pouvez pas utiliser de bibliothèques tierces dans votre base de code, la méthode suivante effectue la tâche:

   /**
     * Returns the string representation of the stack trace.
     *
     * @param throwable the throwable
     * @return the string.
     */
    public static String stackTraceToString(Throwable throwable) {
        StringWriter stringWriter = new StringWriter();
        throwable.printStackTrace(new PrintWriter(stringWriter));
        return stringWriter.toString();
    }

Notez que si vous avez l'intention d'analyser le stacktrace, il est plus simple d'utiliser getStackTrace() et getCause() que d'essayer d'analyser un stacktrace.

Traitement des exceptions interrompues

InterruptedException est une bête déroutante - elle apparaît dans des méthodes apparemment anodines comme Thread.sleep() , mais sa gestion incorrecte conduit à un code difficile à gérer qui se comporte mal dans des environnements concurrents.

À la base, si une InterruptedException est interceptée, cela signifie que quelqu'un, quelque part, appelé Thread.interrupt() sur le thread dans Thread.interrupt() votre code est actuellement exécuté. Vous pourriez être enclin à dire "C'est mon code! Je ne l'interromprai jamais!" " et donc faire quelque chose comme ça:

// Bad. Don't do this.
try {
  Thread.sleep(1000);
} catch (InterruptedException e) {
  // disregard
}

Mais c'est exactement la mauvaise façon de gérer un événement "impossible". Si vous savez que votre application ne rencontrera jamais une InterruptedException vous devez traiter un tel événement comme une violation grave des hypothèses de votre programme et quitter le plus rapidement possible.

La manière correcte de traiter une interruption "impossible" est comme suit:

// When nothing will interrupt your code
try {
  Thread.sleep(1000);
} catch (InterruptedException e) {
  Thread.currentThread().interrupt();
  throw new AssertionError(e);
}

Cela fait deux choses; il restaure d'abord le statut d'interruption du thread (comme si l' InterruptedException n'avait pas été lancée en premier lieu), puis lance une AssertionError indiquant que les invariants de base de votre application ont été violés. Si vous savez avec certitude que vous n'interromprez jamais le thread, ce code s'exécute en toute sécurité car le bloc catch ne doit jamais être atteint.

L'utilisation de la classe Uninterruptibles de Guava permet de simplifier ce modèle; L'appel à Uninterruptibles.sleepUninterruptibly() interrompt l'état interrompu d'un thread jusqu'à ce que la durée de veille expire (à quel moment il est restauré pour que les appels ultérieurs inspectent et lancent leur propre InterruptedException ). Si vous savez que vous n'interromprez jamais ce code, cela évite d'avoir à envelopper vos appels de veille dans un bloc try-catch.

Plus souvent, cependant, vous ne pouvez pas garantir que votre thread ne sera jamais interrompu. En particulier, si vous écrivez du code qui sera exécuté par un Executor ou par un autre gestionnaire de threads, il est essentiel que votre code réponde rapidement aux interruptions, sinon votre application sera bloquée, voire bloquée.

Dans de tels cas, la meilleure chose à faire est généralement de permettre à InterruptedException de propager la pile d'appels, en ajoutant une throws InterruptedException à chaque méthode. Cela peut sembler compliqué mais c'est en fait une propriété souhaitable - les signatures de votre méthode indiquent maintenant aux appelants qu'ils répondront rapidement aux interruptions.

// Let the caller determine how to handle the interrupt if you're unsure
public void myLongRunningMethod() throws InterruptedException {
  ...
}

Dans des cas limités (par exemple, lors du remplacement d'une méthode ne throw aucune exception vérifiée), vous pouvez réinitialiser le statut interrompu sans déclencher une exception, en attendant que le code soit exécuté pour gérer l'interruption. Cela retarde le traitement de l'interruption, mais ne le supprime pas entièrement.

// Suppresses the exception but resets the interrupted state letting later code
// detect the interrupt and handle it properly.
try {
  Thread.sleep(1000);
} catch (InterruptedException e) {
  Thread.currentThread().interrupt();
  return ...; // your expectations are still broken at this point - try not to do more work.
}

La hiérarchie des exceptions Java - Exceptions non vérifiées et vérifiées

Toutes les exceptions Java sont des instances de classes dans la hiérarchie de classes Exception. Cela peut être représenté comme suit:

  • java.lang.Throwable - Il s'agit de la classe de base pour toutes les classes d'exception. Ses méthodes et ses constructeurs implémentent une gamme de fonctionnalités communes à toutes les exceptions.
    • java.lang.Exception - Ceci est la super-classe de toutes les exceptions normales.
      • diverses classes d'exception standard et personnalisées.
      • java.lang.RuntimeException - C'est la super-classe de toutes les exceptions normales qui sont des exceptions non vérifiées .
        • diverses classes d'exceptions d'exécution standard et personnalisées.
    • java.lang.Error - Ceci est la super-classe de toutes les exceptions "d'erreur fatale".

Remarques:

  1. La distinction entre les exceptions cochées et non vérifiées est décrite ci-dessous.
  2. La Throwable , Exception et RuntimeException doit être traitée comme abstract . voir Pitfall - Throwable, Exception, Error ou RuntimeException .
  3. Les exceptions d' Error sont renvoyées par la machine virtuelle Java dans les situations où il serait dangereux ou imprudent qu'une application tente de récupérer.
  4. Il serait imprudent de déclarer des sous-types personnalisés de Throwable . Les outils et bibliothèques Java peuvent supposer que les Error et Exception sont les seuls sous-types directs de Throwable , et se comporter de manière incorrecte si cette hypothèse est incorrecte.

Exceptions vérifiées et non vérifiées

L'une des critiques de la prise en charge des exceptions dans certains langages de programmation est qu'il est difficile de savoir quelles exceptions une méthode ou une procédure donnée peut générer. Étant donné qu'une exception non gérée risque de provoquer le blocage d'un programme, cela peut faire des exceptions une source de fragilité.

Le langage Java résout ce problème avec le mécanisme d'exception vérifié. Tout d'abord, Java classe les exceptions en deux catégories:

  • Les exceptions vérifiées représentent généralement les événements anticipés qu'une application doit pouvoir gérer. Par exemple, IOException et ses sous-types représentent des conditions d'erreur pouvant se produire dans les opérations d'E / S. Les exemples incluent, l'ouverture du fichier échoue car un fichier ou un répertoire n'existe pas, les lectures et écritures réseau échouent car une connexion réseau a été interrompue, etc.

  • Les exceptions non vérifiées représentent généralement des événements imprévus auxquels une application ne peut pas faire face. Ce sont généralement le résultat d'un bogue dans l'application.

(Dans ce qui suit, "renvoyé" fait référence à toute exception envoyée explicitement (par une instruction throw ), ou implicitement (dans un déréférencement ayant échoué, tapez cast, etc.). De même, "propagated" fait référence à une exception lancée dans un appel imbriqué, et non pris en compte dans cet appel. L'exemple de code ci-dessous illustre cela.)

La deuxième partie du mécanisme d'exception vérifié est qu'il existe des restrictions sur les méthodes pour lesquelles une exception vérifiée peut se produire:

  • Lorsqu'une exception vérifiée est lancée ou propagée dans une méthode, elle doit soit être interceptée par la méthode, soit répertoriée dans la clause throws la méthode. (L'importance de la throws clause est décrit dans cet exemple .)
  • Lorsqu'une exception vérifiée est lancée ou propagée dans un bloc d'initialisation, elle doit être interceptée dans le bloc.
  • Une exception vérifiée ne peut pas être propagée par un appel de méthode dans une expression d'initialisation de champ. (Il n'y a aucun moyen d'attraper une telle exception.)

En bref, une exception vérifiée doit être gérée ou déclarée.

Ces restrictions ne s'appliquent pas aux exceptions non vérifiées. Cela inclut tous les cas où une exception est lancée implicitement, car tous ces cas provoquent des exceptions non vérifiées.

Exemples d'exceptions vérifiés

Ces extraits de code sont destinés à illustrer les restrictions d'exception vérifiées. Dans chaque cas, nous affichons une version du code avec une erreur de compilation et une seconde version avec l'erreur corrigée.

// This declares a custom checked exception.
public class MyException extends Exception {
    // constructors omitted.
}

// This declares a custom unchecked exception.
public class MyException2 extends RuntimeException {
    // constructors omitted.
}

Le premier exemple montre comment les exceptions vérifiées peuvent être déclarées "lancées" si elles ne doivent pas être gérées dans la méthode.

// INCORRECT
public void methodThrowingCheckedException(boolean flag) {
    int i = 1 / 0;                // Compiles OK, throws ArithmeticException
    if (flag) {
        throw new MyException();  // Compilation error
    } else {
        throw new MyException2(); // Compiles OK
    }
}

// CORRECTED
public void methodThrowingCheckedException(boolean flag) throws MyException {
    int i = 1 / 0;                // Compiles OK, throws ArithmeticException
    if (flag) {
        throw new MyException();  // Compilation error
    } else {
        throw new MyException2(); // Compiles OK
    }
}

Le deuxième exemple montre comment traiter une exception vérifiée propagée.

// INCORRECT 
public void methodWithPropagatedCheckedException() {
    InputStream is = new FileInputStream("someFile.txt");  // Compilation error
    // FileInputStream throws IOException or a subclass if the file cannot 
    // be opened.  IOException is a checked exception.
    ...
}

// CORRECTED (Version A) 
public void methodWithPropagatedCheckedException() throws IOException {
    InputStream is = new FileInputStream("someFile.txt"); 
    ...
}

// CORRECTED (Version B) 
public void methodWithPropagatedCheckedException() {
    try {
        InputStream is = new FileInputStream("someFile.txt"); 
        ...
    } catch (IOException ex) {
        System.out.println("Cannot open file: " + ex.getMessage());
    }
}

L'exemple final montre comment gérer une exception vérifiée dans un initialiseur de champ statique.

// INCORRECT
public class Test {
    private static final InputStream is = 
            new FileInputStream("someFile.txt");  // Compilation error
}

// CORRECTED
public class Test {
    private static final InputStream is;
    static {
        InputStream tmp = null;
        try {
            tmp = new FileInputStream("someFile.txt");
        } catch (IOException ex) {
            System.out.println("Cannot open file: " + ex.getMessage());
        }
        is = tmp;
    }
}

Notez que dans ce dernier cas, nous devons aussi faire face aux problèmes qui is ne peuvent pas être attribués à plus d'une fois, mais doit également être attribué à, même dans le cas d'une exception.

introduction

Les exceptions sont des erreurs qui surviennent lors de l'exécution d'un programme. Considérons le programme Java ci-dessous qui divise deux entiers.

class Division {
    public static void main(String[] args) {
 
        int a, b, result;
 
        Scanner input = new Scanner(System.in);
        System.out.println("Input two integers");
 
        a = input.nextInt();
        b = input.nextInt();
 
        result = a / b;
 
        System.out.println("Result = " + result);
    }
}

Maintenant, nous compilons et exécutons le code ci-dessus et voyons la sortie pour une tentative de division par zéro:

Input two integers
7 0
Exception in thread "main" java.lang.ArithmeticException: / by zero 
    at Division.main(Disivion.java:14)

Division par zéro est une opération invalide qui produirait une valeur qui ne peut être représentée par un entier. Java gère cela en lançant une exception . Dans ce cas, l'exception est une instance de la classe ArithmeticException .

Remarque: L'exemple de création et de lecture de traces de pile explique la signification de la sortie après les deux nombres.

L'utilitaire d'une exception est le contrôle de flux qu'il autorise. Sans exceptions, une solution typique à ce problème peut être de vérifier si b == 0 :

class Division {
    public static void main(String[] args) {
 
        int a, b, result;

        Scanner input = new Scanner(System.in);
        System.out.println("Input two integers");
 
        a = input.nextInt();
        b = input.nextInt();
 
        if (b == 0) {
            System.out.println("You cannot divide by zero.");
            return;
        }

        result = a / b;
 
        System.out.println("Result = " + result);
    }
}

Ceci imprime le message You cannot divide by zero. à la console et quitte le programme de manière harmonieuse lorsque l'utilisateur essaie de diviser par zéro. Une manière équivalente de traiter ce problème via la gestion des exceptions serait de remplacer le contrôle de flux if par un bloc try-catch :

...

a = input.nextInt();
b = input.nextInt();
 
try {
    result = a / b;
}
catch (ArithmeticException e) {
    System.out.println("An ArithmeticException occurred. Perhaps you tried to divide by zero.");
    return;
}
 
...  

Un bloc try est exécuté comme suit:

  1. Commencez à exécuter le code dans le bloc try .
  2. Si une exception se produit dans le bloc try, abandonnez immédiatement et vérifiez si cette exception est interceptée par le bloc catch (dans ce cas, lorsque l'exception est une instance de ArithmeticException ).
  3. Si l'exception est interceptée , elle est affectée à la variable e et le bloc catch est exécuté.
  4. Si le bloc try ou catch est terminé (c'est-à-dire qu'aucune exception n'est interceptée lors de l'exécution du code), continuez à exécuter le code sous le bloc try-catch .

Il est généralement considéré comme une bonne pratique d'utiliser la gestion des exceptions dans le cadre du contrôle de flux normal d'une application où le comportement serait autrement indéfini ou inattendu. Par exemple, au lieu de retourner null lorsqu'une méthode échoue, il est généralement préférable de lancer une exception pour que l'application utilisant la méthode puisse définir son propre contrôle de flux pour la situation via la gestion des exceptions du type illustré ci-dessus. D'une certaine manière, cela résout le problème de devoir renvoyer un type particulier, étant donné que plusieurs types d' exceptions peuvent être générés pour indiquer le problème spécifique survenu.

Pour plus de conseils sur comment et comment ne pas utiliser les exceptions, reportez-vous à la page sur les pièges Java - Utilisation des exceptions

Retour d'instructions dans le bloc try catch

Bien que ce soit une mauvaise pratique, il est possible d'ajouter plusieurs instructions de retour dans un bloc de gestion des exceptions:

 public static int returnTest(int number){
    try{
        if(number%2 == 0) throw new Exception("Exception thrown");
        else return x;
    }
    catch(Exception e){
        return 3;
    }
    finally{
        return 7;
    }
}

Cette méthode renverra toujours 7 car le bloc finally associé au bloc try / catch est exécuté avant que tout soit renvoyé. Maintenant, comme a finalement le return 7; , cette valeur remplace les valeurs de retour try / catch.

Si le bloc catch retourne une valeur primitive et que cette valeur primitive est modifiée ultérieurement dans le bloc finally, la valeur renvoyée dans le bloc catch sera renvoyée et les modifications du bloc finally seront ignorées.

L'exemple ci-dessous affichera "0" et non "1".

public class FinallyExample {

    public static void main(String[] args) {
        int n = returnTest(4);
        
        System.out.println(n);
    }

    public static int returnTest(int number) {
        
        int returnNumber = 0; 
        
        try {
            if (number % 2 == 0)
                throw new Exception("Exception thrown");
            else
                return returnNumber;
        } catch (Exception e) {
            return returnNumber;
        } finally {
            returnNumber = 1;
        }
    }
}

Fonctionnalités avancées des exceptions

Cet exemple couvre certaines fonctionnalités avancées et cas d'utilisation des exceptions.

Examiner le callstack par programmation

Java SE 1.4

La principale utilisation des stacktraces d'exception est de fournir des informations sur une erreur d'application et son contexte, de sorte que le programmeur puisse diagnostiquer et résoudre le problème. Parfois, il peut être utilisé pour d'autres choses. Par exemple, une classe SecurityManager peut avoir besoin d'examiner la pile d'appels pour décider si le code qui effectue un appel doit être approuvé.

Vous pouvez utiliser des exceptions pour examiner la pile d'appels par programme de la manière suivante:

    Exception ex = new Exception();   // this captures the call stack
    StackTraceElement[] frames = ex.getStackTrace();
    System.out.println("This method is " + frames[0].getMethodName());
    System.out.println("Called from method " + frames[1].getMethodName());

Il y a quelques mises en garde importantes à ce sujet:

  1. Les informations disponibles dans un StackTraceElement sont limitées. Il n'y a pas plus d'informations disponibles que celles affichées par printStackTrace . (Les valeurs des variables locales dans le cadre ne sont pas disponibles.)

  2. Javadocs pour getStackTrace() indique qu'une machine virtuelle getStackTrace() est autorisée à laisser de côté les cadres:

    Certaines machines virtuelles peuvent, dans certaines circonstances, omettre un ou plusieurs cadres de pile de la trace de pile. Dans le cas extrême, une machine virtuelle qui ne dispose d'aucune information de suivi de pile concernant ce produit jetable est autorisée à renvoyer un tableau de longueur nulle à partir de cette méthode.

Optimiser la construction des exceptions

Comme mentionné ailleurs, la construction d'une exception est assez coûteuse car elle implique la capture et l'enregistrement d'informations sur tous les cadres de pile du thread en cours. Parfois, nous savons que cette information ne sera jamais utilisée pour une exception donnée; par exemple, le stacktrace ne sera jamais imprimé. Dans ce cas, il existe un truc d'implémentation que nous pouvons utiliser dans une exception personnalisée pour que les informations ne soient pas capturées.

Les informations sur les trames de pile nécessaires pour les stacktraces sont capturées lorsque les constructeurs Throwable appellent la méthode Throwable.fillInStackTrace() . Cette méthode est public , ce qui signifie qu'une sous-classe peut la remplacer. L'astuce consiste à remplacer la méthode héritée de Throwable par une méthode qui ne fait rien; par exemple

  public class MyException extends Exception {
      // constructors

      @Override 
      public void fillInStackTrace() {
          // do nothing
      }
  }

Le problème avec cette approche est qu'une exception qui remplace fillInStackTrace() ne peut jamais capturer la trace de la pile et est inutile dans les scénarios où vous en avez besoin.

Effacer ou remplacer le stacktrace

Java SE 1.4

Dans certains cas, le stacktrace d'une exception créée de manière normale contient des informations incorrectes ou des informations que le développeur ne souhaite pas révéler à l'utilisateur. Pour ces scénarios, Throwable.setStackTrace peut être utilisé pour remplacer le tableau d'objets StackTraceElement les informations.

Par exemple, les éléments suivants peuvent être utilisés pour ignorer les informations de pile d'une exception:

 exception.setStackTrace(new StackTraceElement[0]);

Suppression d'exceptions

Java SE 7

Java 7 a introduit la construction try-with-resources et le concept associé de suppression des exceptions. Considérez l'extrait suivant:

try (Writer w = new BufferedWriter(new FileWriter(someFilename))) {
    // do stuff
    int temp = 0 / 0;    // throws an ArithmeticException
}

Lorsque l'exception est levée, try appelle close() sur le w qui FileWriter toute sortie mise en mémoire tampon et fermera ensuite FileWriter . Mais que se passe-t-il si une IOException est lancée lors du vidage de la sortie?

Qu'est-ce qui se passe est que toute exception qui est levée lors du nettoyage d'une ressource est supprimée . L'exception est interceptée et ajoutée à la liste des exceptions supprimées de l'exception principale. Ensuite, le try-with-resources continuera avec le nettoyage des autres ressources. Enfin, l’exception primaire sera relancée.

Un modèle similaire se produit si une exception est générée lors de l'initialisation de la ressource ou si le bloc try termine normalement. La première exception lancée devient la principale exception, et celles qui découlent du nettoyage sont supprimées.

Les exceptions supprimées peuvent être extraites de l'objet exception primaire en appelant getSuppressedExceptions .

Les instructions try-finally et try-catch-finally

L'instruction try...catch...finally combine la gestion des exceptions avec le code de nettoyage. Le bloc finally contient le code qui sera exécuté dans toutes les circonstances. Cela les rend appropriés pour la gestion des ressources et d'autres types de nettoyage.

Essayez-enfin

Voici un exemple de la forme plus simple ( try...finally ):

try {
    doSomething();  
} finally {
    cleanUp();
}

Le comportement du try...finally se présente comme suit:

  • Le code dans le bloc try est exécuté.
  • Si aucune exception n'a été émise dans le bloc try :
    • Le code dans le bloc finally est exécuté.
    • Si le bloc finally lève une exception, cette exception est propagée.
    • Sinon, le contrôle passe à l'instruction suivante après l' try...finally .
  • Si une exception a été émise dans le bloc try:
    • Le code dans le bloc finally est exécuté.
    • Si le bloc finally lève une exception, cette exception est propagée.
    • Sinon, l'exception d'origine continue à se propager.

Le code dans finally bloc sera toujours exécuté. (Les seules exceptions sont si System.exit(int) est appelé ou si la JVM panique.) Ainsi, un bloc finally est le code de lieu correct qui doit toujours être exécuté; fermeture de fichiers et autres ressources ou libération de verrous.

essayer-enfin

Notre deuxième exemple montre comment catch et finally peuvent être utilisés ensemble. Cela montre également que le nettoyage des ressources n'est pas simple.

// This code snippet writes the first line of a file to a string
String result = null;
Reader reader = null;
try {
    reader = new BufferedReader(new FileReader(fileName));
    result = reader.readLine();
} catch (IOException ex) {
    Logger.getLogger.warn("Unexpected IO error", ex);  // logging the exception
} finally {
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException ex) {
            // ignore / discard this exception
        }
    }
}

L'ensemble complet des comportements (hypothétiques) de try...catch...finally dans cet exemple est trop compliqué à décrire ici. La version simple est que le code dans le bloc finally sera toujours exécuté.

En regardant cela du point de vue de la gestion des ressources:

  • Nous déclarons la "ressource" (c.-à reader variable reader ) avant le bloc try afin qu'elle soit possible pour le bloc finally .
  • En mettant le new FileReader(...) , le catch est capable de gérer toute exception IOError lors de l'ouverture du fichier.
  • Nous avons besoin d'un reader.close() dans le bloc finally car il existe des chemins d'exception que nous ne pouvons pas intercepter ni dans le bloc try ni dans le bloc catch .
  • Cependant, une exception pouvant avoir été émise avant l'initialisation du reader , nous avons également besoin d'un test null explicite.
  • Enfin, l’appel reader.close() pourrait (hypothétiquement) lancer une exception. Nous ne nous soucions pas de cela, mais si nous n'attrapons pas l'exception à la source, nous aurons besoin de la traiter plus haut dans la pile des appels.
Java SE 7

Java 7 et les versions ultérieures fournissent une syntaxe alternative à l'utilisation des ressources qui simplifie considérablement le nettoyage des ressources.

La clause 'throws' dans une déclaration de méthode

Le mécanisme d' exception vérifié de Java nécessite que le programmeur déclare que certaines méthodes peuvent générer des exceptions vérifiées spécifiées. Ceci est fait à l' aide de la throws clause. Par exemple:

public class OddNumberException extends Exception { // a checked exception
}

public void checkEven(int number) throws OddNumberException {
    if (number % 2 != 0) {
        throw new OddNumberException();
    }
}

throws OddNumberException déclenche une throws OddNumberException indiquant qu'un appel à checkEven peut checkEven une exception de type OddNumberException .

Une throws clause peut déclarer une liste des types, et peut inclure des exceptions non vérifiées ainsi que les exceptions vérifiées.

public void checkEven(Double number) 
        throws OddNumberException, ArithmeticException {
    if (!Double.isFinite(number)) {
        throw new ArithmeticException("INF or NaN");
    } else if (number % 2 != 0) {
        throw new OddNumberException();
    }
}

Quel est l'intérêt de déclarer des exceptions non vérifiées comme levées?

La throws clause dans une déclaration de méthode a deux objectifs:

  1. Il indique au compilateur quelles exceptions sont générées pour que le compilateur puisse signaler les exceptions non détectées (vérifiées) en tant qu'erreurs.

  2. Il indique à un programmeur qui écrit du code qui appelle la méthode quelles exceptions attendre. À cette fin, il est souvent logique d'inclure des exceptions non vérifiées dans une liste de throws .

Remarque: la liste de throws est également utilisée par l’outil javadoc lors de la génération de la documentation de l’API, ainsi que par les astuces de la méthode typique de texte de survol de l’EDI.

Les jetons et la méthode

Les throws formes clause partie de la signature d'une méthode dans le but de passer outre la méthode. Une méthode de substitution peut être déclarée avec le même ensemble d'exceptions vérifiées que celles générées par la méthode substituée ou avec un sous-ensemble. Toutefois, la méthode de remplacement ne peut pas ajouter des exceptions vérifiées supplémentaires. Par exemple:

@Override
public void checkEven(int number) throws NullPointerException // OK—NullPointerException is an unchecked exception
    ...

@Override
public void checkEven(Double number) throws OddNumberException // OK—identical to the superclass
    ...

class PrimeNumberException extends OddNumberException {}
class NonEvenNumberException extends OddNumberException {}

@Override
public void checkEven(int number) throws PrimeNumberException, NonEvenNumberException // OK—these are both subclasses

@Override
public void checkEven(Double number) throws IOExcepion         // ERROR

La raison de cette règle est que si une méthode de substitution peut lancer une exception vérifiée que la méthode surchargée ne peut pas lancer, cela pourrait casser la substituabilité du type.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow