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(...), jamaisthrow 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 invalides —
IllegalArgumentExceptionpour « vous m'avez appelé incorrectement. » - État incorrect —
IllegalStateExceptionpour « 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 argumentIllegalStateException— état incorrectNullPointerException— un argument requis était null (utilisezObjects.requireNonNull)UnsupportedOperationException— opération non implémentée (par ex.addd'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
RuntimeExceptionet 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 IOExceptiondéclare l'exception vérifiée qui peut provenir dehttpClient.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 clausethrows.
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.
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.