Java EE - EPSI POE mars 2017 - David Gayerie Licence Creative Commons

Principe général

Une exception permet d'interrompre un traitement lorsqu'une situation exceptionnelle se produit.

Une exception est propagée dans la pile des appels de méthodes. Elle peut être interceptée progammatiquement. Si elle n'est interceptée par aucune méthode, elle remonte la pile d'appel et interrompt le thread d'exécution. S'il s'agit du thread principal, le programme s'interrompt en erreur.

Déclaration et héritage

Les exceptions sont représentées par des classes qui ont comme ancêtre java.lang.Throwable

La hiérarchie de base des exceptions

Les classes héritant de java.lang.Error sont réservées pour la JVM afin de signaler des erreurs systèmes :

Pour créer une exception, il suffit de créer sa propre classe en la faisant hériter de java.lang.Exception.


// La classe hérite de Exception qui hérite elle-même de throwable.
// Par convention le nom de la classe se termine par Exception.
public class EndOfTheWorldException extends Exception {
}

Pour lancer une exception on utilise le mot-clé throw. On peut maintenant signaler l'événement exceptionnel de la fin du monde en lançant l'exception :


  throw new EndOfTheWorldException();

On distingue deux catégories d'exception : les checked exceptions et les unchecked exceptions

Les checked exceptions

Les checked exceptions doivent apparaître dans la signature des méthodes qui peuvent les lancer ou les propager lors d'un appel grâce au mot-clé throws.


public void pressTheRedButton() throws EndOfTheWorldException {
  if(this.isTheEndOfTheWorld()) {
    throw new EndOfTheWorldException();
  }
  // ...
}

public void doSomethingSilly() throws EndOfTheWorldException {
  pressTheRedButton();
}

Plusieurs checked exceptions peuvent être spécifiées en les séparant par une virgule :


  public Stuff buy(long amount, Currency currency) 
    throws NotEnoughMoneyException, NotAcceptedCurrencyException, 
           StuffUnavailableException {
    // ...
  }

Les unchecked exceptions

Les unchecked exceptions héritent directement ou indirectement de RuntimeException ou de Error.

La déclaration de ces exceptions est optionnelle dans la signature des méthodes pouvant les lancer ou les propager. D'où leur nom de unchecked qui signifie que leur propagation n'est pas vérifiée par le compilateur.


  public class EntityNotFoundException extends RuntimeException {
    public EntityNotFoundException(String message) {
      super(message);
    } 
  }


  // Il n'est pas nécessaire d'ajouter :
  //       throws EntityNotFoundException
  // car EntityNotFoundException hérite de RuntimeException.
  public Entity get(String id) {
    Entity entity = database.load(id);
    if (entity == null) {
      throw new EntityNotFoundException("Cannot find entity " + id);
    }
    return entity;
  }

La syntaxe try-catch

Une exception peut être interceptée par une méthode dans la pile d'appel grâce à la syntaxe try-catch :


  try {
    evilMind.pressTheRedButton();
  }
  catch (EndOfTheWorldException ex) {
    hero.saveTheWorld();
  }

Le block défini par le mot-clé catch est exécuté uniquement si une exception de type EndOfTheWorldException est lancée lors de l'exécution du block défini par le mot-clé try.

Un block défini par le mot-clé finally est exécuté systématiquement après un block try et un block catch (si une exception a été attrapée).


  try {
    evilMind.pressTheRedButton();
  }
  catch (EndOfTheWorldException ex) {
    hero.saveTheWorld();
  }
  finally {
    // Finalement, le héros arrête l'esprit du mal qu'il ait eu
    // ou non à sauver le monde.
    hero.arrest(evilMind);
  }

Un block finally est souvent utilisé en Java pour libérer des ressources système à la fin d'un traitement.


public String getFileContent(String filename) throws java.io.IOException {
  java.io.FileReader reader = new java.io.FileReader(filename);
  try {
    int nbCharRead = 0;
    char[] buffer = new char[1024];
    StringBuilder builder = new StringBuilder();
    // L'appel à reader.read peut lancer une java.io.IOException
    while ((nbCharRead = reader.read(buffer)) >= 0) {
      builder.append(buffer, 0, nbCharRead);
    }
    // le retour explicite n'empêche pas l'exécution du block finally.
    return builder.toString();
  }
  // Ce block est obligatoirement exécuté après le block try.
  // Ainsi le flux de lecture sur le fichier est fermé 
  // avant le retour de la méthode. 
  finally {
    reader.close();
  }
}

Depuis Java 7, on peut utiliser une expression try-with-resources. Pour gérer le cas particulier des resources que l'on souhaite fermer automatiquement.


public String getFileContent(String filename) throws java.io.IOException {
  // La resource que l'on souhaite fermer automatiquement et
  // déclarer après le mot-clé try entre parenthèses.  
  try (java.io.FileReader reader = new java.io.FileReader(filename)) {
    int nbCharRead = 0;
    char[] buffer = new char[1024];
    StringBuilder builder = new StringBuilder();
    while ((nbCharRead = reader.read(buffer)) >= 0) {
      builder.append(buffer, 0, nbCharRead);
    }
    return builder.toString();
  }
  // On évite l'utilisation du bloc finally de l'exemple
  // précédent. Le try-with-resource garantit que reader.close()
  // est appelé.
}

Seules les classes implémentant l'interface java.lang.AutoCloseable ou java.io.Closeable peuvent être utilisées dans une expression try-with-resources.