W3docs

Java throw et throws

Lancez des exceptions en Java avec throw et déclarez-les dans les signatures de méthode avec throws.

Jusqu'ici, nous avons intercepté des exceptions lancées par d'autres codes. Vous allez maintenant voir comment lancer les vôtres. Deux mots-clés font l'essentiel du travail — et ils sont faciles à confondre car ils ne diffèrent que d'une lettre.

  • throw — une instruction qui lève une exception à l'exécution. Un seul mot, dans du code qui s'exécute.
  • throws — une déclaration dans une signature de méthode qui signifie « cette méthode peut lever ces types d'exceptions. » Elle est vérifiée par le compilateur, et ne s'exécute jamais.

throw se produit. throws prévient. Gardez ce duo à l'esprit.

Lancer une exception

throw prend une expression de type Throwable (ou tout sous-type) et la lève. La méthode courante se termine immédiatement, la pile commence à se dérouler, et l'exception part à la recherche d'un catch correspondant.

if (amount < 0) {
  throw new IllegalArgumentException("amount must be non-negative, got " + amount);
}

Trois détails :

  • Vous ne pouvez lancer qu'un Throwable. Le compilateur l'impose — throw "oops"; ne compilera pas.
  • Vous lancez toujours une instance, pas une classe. throw new X(...), jamais throw X.
  • L'instance peut être construite à la volée (courant), ou être un objet préexistant (rare — les exceptions portent des traces de pile depuis leur construction, donc réutiliser une instance fige la mauvaise trace).

Quand lancer

Lancez une exception quand la méthode courante ne peut pas remplir son contrat. Quelques cas évidents :

  • Arguments invalidesIllegalArgumentException pour « vous m'avez appelé incorrectement. »
  • État incorrectIllegalStateException pour « vous m'avez appelé au mauvais moment » (par ex. next() sur un itérateur vide).
  • Données manquantes — exceptions spécifiques au domaine comme UserNotFoundException.
  • Opérations externes échouées — erreurs d'E/S, erreurs réseau. Généralement, elles proviennent de l'appel que vous venez de faire, donc vous ne les construisez pas vous-même ; vous les laissez se propager, ou vous les encapsulez dans une exception de plus haut niveau.

Le cas où il ne faut pas lancer : comme raccourci de flux de contrôle pour des résultats normaux. « Lancer pour le flux de contrôle » est lent et déroutant. Si « introuvable » est un résultat courant, renvoyez un Optional<T>, pas un NotFoundException.

Choisir un type

Les exceptions intégrées de java.lang couvrent la plupart des cas sans cérémonie :

  • IllegalArgumentException — mauvais argument
  • IllegalStateException — état incorrect
  • NullPointerException — un argument requis était null (utilisez Objects.requireNonNull)
  • UnsupportedOperationException — opération non implémentée (par ex. add d'une liste immuable)
  • ArithmeticException — erreur arithmétique

Lorsque l'échec est spécifique à votre domaine — « utilisateur introuvable », « coupon invalide », « configuration désynchronisée » — écrivez une petite classe personnalisée pour cela. Deux chapitres plus loin, nous ferons exactement cela.

La clause throws

Si votre méthode peut lever une exception vérifiée qu'elle n'intercepte pas elle-même, vous devez la déclarer :

public Config loadConfig(Path p) throws IOException, ParseException {
  String text = Files.readString(p);
  return parser.parse(text);
}

La clause fait partie du contrat de la méthode. Elle indique à chaque appelant : « si vous m'appelez, vous devez soit intercepter ces exceptions, soit les déclarer vous-mêmes. » Le compilateur l'impose — c'est ce qui les rend vérifiées.

Quelques règles :

  • Vous ne déclarez que des exceptions vérifiées. Les RuntimeException et leurs sous-classes sont non vérifiées — les déclarer est permis mais pas obligatoire, et généralement pas fait.
  • Vous pouvez déclarer plus de types que vous n'en levez réellement — utile pour garder l'option ouverte pour de futures implémentations, bien que ce soit un léger bruit.
  • Une méthode qui surcharge une autre peut déclarer les mêmes ou moins d'exceptions vérifiées que le parent (et uniquement des sous-types des exceptions déclarées). Elle ne peut pas en ajouter de nouvelles. C'est la substitution de Liskov appliquée aux exceptions.

throw et throws ensemble

Une méthode réelle utilise généralement les deux :

public User loadUser(String id) throws IOException {
  if (id == null || id.isBlank()) {
    throw new IllegalArgumentException("id must be non-blank");
  }
  String json = httpClient.get("/users/" + id);   // may throw IOException
  return parser.toUser(json);
}
  • Le throws IOException déclare l'exception vérifiée qui peut provenir de httpClient.get.
  • Le throw new IllegalArgumentException(...) lève une exception non vérifiée pour une entrée invalide. Elle n'a pas besoin d'apparaître dans la clause throws.

Encapsuler une exception

Lorsqu'une exception de bas niveau n'est pas significative à votre niveau, encapsulez-la dans une exception qui l'est. Passez l'originale comme cause pour que la trace reste intacte :

try {
  return Files.readString(configPath);
} catch (IOException e) {
  throw new ConfigLoadException("could not load config from " + configPath, e);
}

Le pattern du constructeur à deux arguments — (message, cause) — est standard dans Exception, IOException, et tous les types intégrés. Lorsque vous écrivez votre propre classe d'exception, donnez-lui les deux constructeurs.

Un exemple complet

Un petit utilitaire de style bancaire qui valide les entrées avec IllegalArgumentException, signale un compte vide avec IllegalStateException, et laisse une exception vérifiée se propager à l'appelant via throws. Le programme principal montre à quoi ressemble chacune lorsqu'elle est levée.

java— editable, runs on the server

Les trois cas à l'exécution — argument, état, et retrait réussi — passent par un seul catch. Le quatrième, archive(), ne compile que parce que main est autorisé à intercepter Exception et parce que archive() a déclaré throws ArchiveException. Essayez de supprimer la clause throws et le programme ne compilera plus.

La suite

Le compilateur traite certaines exceptions strictement (vous devez les gérer) et d'autres de manière permissive (vous n'avez pas à le faire). Cette distinction fait l'objet du prochain chapitre. Continuez vers Java checked vs. unchecked exceptions.

Pratique

Pratique
Une signature de méthode indique `public void save() throws IOException`. Le corps de la méthode est vide — il ne lève rien. Va-t-elle compiler ?
Une signature de méthode indique `public void save() throws IOException`. Le corps de la méthode est vide — il ne lève rien. Va-t-elle compiler ?
Was this page helpful?