Comment puis-je lever des exceptions CHECKED depuis des flux / lambdas Java 8?
En d'autres termes, je veux compiler du code comme celui-ci:
public List<Class> getClasses() throws ClassNotFoundException {
List<Class> classes =
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
.map(className -> Class.forName(className))
.collect(Collectors.toList());
return classes;
}
Ce code ne se compile pas, car la Class.forName()
méthode ci-dessus lance ClassNotFoundException
, qui est vérifiée.
Veuillez noter que je ne veux PAS encapsuler l'exception cochée dans une exception d'exécution et lever l'exception non cochée encapsulée à la place. Je veux lancer l'exception vérifiée elle - même , et sans ajouter laid try
/ catches
au flux.
throws ClassNotFoundException
dans la déclaration de méthode qui contient le flux, de sorte que le code appelant attendra et sera autorisé à intercepter l'exception vérifiée.Réponses:
La réponse simple à votre question est: vous ne pouvez pas, du moins pas directement. Et ce n'est pas de ta faute. Oracle a tout gâché. Ils s'accrochent au concept d'exceptions vérifiées, mais ont inconsciemment oublié de prendre soin des exceptions vérifiées lors de la conception des interfaces fonctionnelles, des flux, de lambda, etc. C'est tout le grain de sel du moulin d'experts comme Robert C.Martin qui appelle les exceptions vérifiées une expérience ratée.
À mon avis, c'est un énorme bogue dans l' API et un bogue mineur dans la spécification du langage .
Le bogue dans l'API est qu'il ne fournit aucune fonction pour transmettre des exceptions vérifiées où cela aurait en fait beaucoup de sens pour la programmation fonctionnelle. Comme je vais le démontrer ci-dessous, une telle installation aurait été facilement possible.
Le bogue dans la spécification de langage est qu'il ne permet pas à un paramètre de type de déduire une liste de types au lieu d'un seul type tant que le paramètre de type n'est utilisé que dans les situations où une liste de types est autorisée (
throws
clause).Notre attente en tant que programmeurs Java est que le code suivant soit compilé:
Cependant, cela donne:
La façon dont les interfaces fonctionnelles sont définies empêche actuellement le compilateur de transmettre l'exception - il n'y a pas de déclaration qui le dirait
Stream.map()
siFunction.apply() throws E
,Stream.map() throws E
aussi.Ce qui manque, c'est une déclaration d'un paramètre de type pour passer par les exceptions vérifiées. Le code suivant montre comment un tel paramètre de type pass-through aurait pu être déclaré avec la syntaxe actuelle. À l'exception du cas spécial de la ligne marquée, qui est une limite décrite ci-dessous, ce code se compile et se comporte comme prévu.
Dans le cas de
throwSomeMore
nous aimerions voirIOException
manquer, mais il manque réellementException
.Ce n'est pas parfait car l'inférence de type semble rechercher un seul type, même dans le cas d'exceptions. Parce que l'inférence de type a besoin d'un seul type,
E
doit être résolue en un communsuper
deClassNotFoundException
etIOException
, ce qui estException
.Un ajustement de la définition de l'inférence de type est nécessaire pour que le compilateur recherche plusieurs types si le paramètre type est utilisé lorsqu'une liste de types est autorisée (
throws
clause). Le type d'exception signalé par le compilateur serait alors aussi spécifique que lathrows
déclaration d' origine des exceptions vérifiées de la méthode référencée, et non un super-type fourre-tout.La mauvaise nouvelle est que cela signifie qu'Oracle a tout gâché. Certes, ils ne casseront pas le code utilisateur-terre, mais l'introduction de paramètres de type exception aux interfaces fonctionnelles existantes romprait la compilation de tout le code utilisateur-terre qui utilise ces interfaces explicitement. Ils devront inventer un nouveau sucre de syntaxe pour résoudre ce problème.
La pire nouvelle est que ce sujet a déjà été discuté par Brian Goetz en 2010 https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java (nouveau lien: http://mail.openjdk.java.net/pipermail/lambda -dev / 2010-June / 001484.html ) mais je suis informé que cette enquête n'a finalement pas abouti et qu'il n'y a aucun travail en cours chez Oracle à ma connaissance pour atténuer les interactions entre les exceptions vérifiées et les lambdas.
la source
try-catch
bloc à l' intérieur du lambda, et cela n'a tout simplement aucun sens. Dès qu'ilClass.forName
est utilisé d'une manière ou d'une autre dans le lambda, par exemple dansnames.forEach(Class::forName)
, le problème est là. Fondamentalement, les méthodes qui génèrent des exceptions vérifiées ont été exclues de la participation à la programmation fonctionnelle en tant qu'interfaces fonctionnelles directement, par (mauvaise!) Conception.Cette
LambdaExceptionUtil
classe d'assistance vous permet d'utiliser toutes les exceptions vérifiées dans les flux Java, comme ceci:Remarque
Class::forName
jetteClassNotFoundException
, ce qui est vérifié . Le flux lui-même lève égalementClassNotFoundException
, et NON une exception non contrôlée.Beaucoup d'autres exemples sur la façon de l'utiliser (après l'importation statique
LambdaExceptionUtil
):REMARQUE 1: Les
rethrow
méthodes de laLambdaExceptionUtil
classe ci-dessus peuvent être utilisées sans crainte et sont utilisables dans toutes les situations . Un grand merci à l'utilisateur @PaoloC qui a aidé à résoudre le dernier problème: Maintenant, le compilateur vous demandera d'ajouter des clauses throw et tout comme si vous pouviez lancer des exceptions vérifiées nativement sur les flux Java 8.REMARQUE 2: Les
uncheck
méthodes de laLambdaExceptionUtil
classe ci-dessus sont des méthodes bonus et peuvent être supprimées de la classe en toute sécurité si vous ne souhaitez pas les utiliser. Si vous les avez utilisés, faites-le avec soin, et pas avant d'avoir compris les cas d'utilisation, avantages / inconvénients et limites suivants:• Vous pouvez utiliser les
uncheck
méthodes si vous appelez une méthode qui ne peut littéralement jamais lever l'exception qu'elle déclare. Par exemple: new String (byteArr, "UTF-8") lève UnsupportedEncodingException, mais UTF-8 est garanti par la spécification Java d'être toujours présent. Ici, la déclaration des lancers est une nuisance et toute solution pour la faire taire avec un passe-partout minimal est la bienvenue:String text = uncheck(() -> new String(byteArr, "UTF-8"));
• Vous pouvez utiliser les
uncheck
méthodes si vous implémentez une interface stricte où vous n'avez pas la possibilité d'ajouter une déclaration throws, et pourtant, lever une exception est tout à fait approprié. Envelopper une exception juste pour obtenir le privilège de la lancer entraîne une trace de pile avec de fausses exceptions qui ne fournissent aucune information sur ce qui s'est réellement passé. Un bon exemple est Runnable.run (), qui ne lève aucune exception vérifiée.• Dans tous les cas, si vous décidez d'utiliser les
uncheck
méthodes, soyez conscient de ces 2 conséquences de lever des exceptions CHECKED sans clause throws: 1) Le code appelant ne pourra pas l'attraper par son nom (si vous essayez, le le compilateur dira: L'exception n'est jamais levée dans le corps de l'instruction try correspondante). Il bouillonnera et sera probablement pris dans la boucle du programme principal par une "exception de capture" ou "catch Throwable", qui peut être ce que vous voulez quand même. 2) Il viole le principe de la moindre surprise: il ne suffira plus de pêcherRuntimeException
pour pouvoir garantir la capture de toutes les exceptions possibles. Pour cette raison, je crois que cela ne devrait pas être fait dans le code cadre, mais seulement dans le code métier que vous contrôlez complètement.la source
@SuppressWarnings ("unchecked")
supercherie du compilateur est totalement inacceptable.Vous ne pouvez pas faire cela en toute sécurité. Vous pouvez tricher, mais votre programme est interrompu et cela reviendra inévitablement pour mordre quelqu'un (ça devrait être vous, mais souvent notre tricherie explose sur quelqu'un d'autre.)
Voici une façon légèrement plus sûre de le faire (mais je ne le recommande toujours pas.)
Ici, ce que vous faites est d'attraper l'exception dans le lambda, de lancer un signal hors du pipeline de flux qui indique que le calcul a échoué de manière exceptionnelle, d'attraper le signal et d'agir sur ce signal pour lever l'exception sous-jacente. La clé est que vous interceptez toujours l'exception synthétique, plutôt que de permettre à une exception vérifiée de fuir sans déclarer que l'exception est levée.
la source
Function
etc ne fontthrows
rien; Je suis juste curieux.throw w.cause;
ne ferait pas se plaindre le compilateur que la méthode ne lance ni n'attrapeThrowable
? Il est donc probable qu'un castingIOException
sera nécessaire à cet endroit. De plus, si le lambda lève plus d'un type d'exception vérifiée, le corps de la capture deviendrait quelque peu laid avec quelquesinstanceof
vérifications (ou autre chose avec un objectif similaire) pour vérifier quelle exception vérifiée était levée.Vous pouvez!
Étendre @marcg
UtilException
et ajouterthrow E
si nécessaire: de cette façon, le compilateur vous demandera d'ajouter des clauses throw et tout comme si vous pouviez lancer des exceptions vérifiées nativement sur les flux de java 8.Instructions: copiez / collez simplement
LambdaExceptionUtil
votre IDE puis utilisez-le comme indiqué ci-dessousLambdaExceptionUtilTest
.Quelques tests pour montrer l'utilisation et le comportement:
la source
<Integer>
avantmap
. En fait, le compilateur java ne peut pas déduire leInteger
type de retour. Tout le reste devrait être correct.Exception
.Utilisez simplement l'une des NoException (mon projet), les Unchecked de jOOλ , throwing-lambdas , les interfaces Throwable ou Faux Pas .
la source
J'ai écrit une bibliothèque qui étend l'API Stream pour vous permettre de lever des exceptions vérifiées. Il utilise le tour de Brian Goetz.
Votre code deviendrait
la source
Cette réponse est similaire à 17 mais en évitant la définition d'exception de wrapper:
la source
Vous ne pouvez pas.
Cependant, vous voudrez peut-être jeter un œil à l' un de mes projets qui vous permet de manipuler plus facilement de tels "lancer des lambdas".
Dans votre cas, vous pourriez le faire:
et attraper
MyException
.C’est un exemple. Un autre exemple est que vous pourriez avoir
.orReturn()
une valeur par défaut.Notez qu'il s'agit toujours d'un travail en cours, d'autres sont à venir. De meilleurs noms, plus de fonctionnalités, etc.
la source
.orThrowChecked()
méthode à votre projet qui permet de lever l'exception cochée elle-même . Jetez un œil à maUtilException
réponse sur cette page et voyez si vous aimez l'idée d'ajouter cette troisième possibilité à votre projet.Stream
implémenteAutoCloseable
?MyException
ci - dessus doit-il être une exception non contrôlée?En résumant les commentaires ci-dessus, la solution avancée consiste à utiliser un wrapper spécial pour les fonctions non contrôlées avec un générateur comme l'API qui fournit la récupération, la reprise et la suppression.
Le code ci-dessous le démontre pour les interfaces consommateur, fournisseur et fonction. Il peut être facilement étendu. Certains mots clés publics ont été supprimés pour cet exemple.
Class Try est le point de terminaison du code client. Les méthodes sûres peuvent avoir un nom unique pour chaque type de fonction. CheckedConsumer , CheckedSupplier et CheckedFunction sont des analogues vérifiés des fonctions lib qui peuvent être utilisées indépendamment de Try
CheckedBuilder est l'interface pour gérer les exceptions dans certaines fonctions vérifiées. orTry permet d'exécuter une autre fonction de même type si la précédente a échoué. handle fournit la gestion des exceptions, y compris le filtrage des types d'exception. L'ordre des gestionnaires est important. Réduisez les méthodes non sécurisées et relancez la dernière exception de la chaîne d'exécution. Les méthodes orElse et orElseGet retournent des valeurs alternatives comme celles facultatives si toutes les fonctions ont échoué. Il existe également une méthode de suppression . CheckedWrapper est l'implémentation courante de CheckedBuilder.
la source
TL; DR Utilisez simplement Lombok's
@SneakyThrows
.Christian Hujer a déjà expliqué en détail pourquoi lever des exceptions vérifiées à partir d'un flux n'est pas, à proprement parler, possible en raison des limitations de Java.
Certaines autres réponses ont expliqué des astuces pour contourner les limites de la langue tout en étant en mesure de répondre à l'exigence de lancer "l'exception vérifiée elle-même, et sans ajouter de vilains essais / captures au flux" , certains d'entre eux nécessitant des dizaines de lignes supplémentaires de passe-partout.
Je vais souligner une autre option pour ce faire que IMHO est beaucoup plus propre que toutes les autres: celle de Lombok
@SneakyThrows
. Il a été mentionné en passant par d'autres réponses mais a été un peu enterré sous beaucoup de détails inutiles.Le code résultant est aussi simple que:
Nous avions juste besoin d'une
Extract Method
refactorisation (effectuée par l'IDE) et d' une ligne supplémentaire pour@SneakyThrows
. L'annotation prend en charge l'ajout de tous les passe-partout pour vous assurer que vous pouvez lever votre exception vérifiée sans l'envelopperRuntimeException
et sans avoir besoin de la déclarer explicitement.la source
Vous pouvez également écrire une méthode d'encapsuleur pour encapsuler les exceptions non contrôlées et même améliorer l'encapsuleur avec un paramètre supplémentaire représentant une autre interface fonctionnelle (avec le même type de retour R ). Dans ce cas, vous pouvez passer une fonction qui serait exécutée et retournée en cas d'exceptions. Voir l'exemple ci-dessous:
la source
Voici une vue ou une solution différente pour le problème d'origine. Ici, je montre que nous avons une option pour écrire un code qui ne traitera qu'un sous-ensemble valide de valeurs avec une option pour détecter et gérer les cas lorsque l'exception a été levée.
la source
Je suis d'accord avec les commentaires ci-dessus, en utilisant Stream.map, vous êtes limité à l'implémentation de Function qui ne lève pas d'exceptions.
Vous pouvez cependant créer votre propre FunctionalInterface qui lance comme ci-dessous.
puis implémentez-le en utilisant Lambdas ou des références comme indiqué ci-dessous.
la source
La seule façon intégrée de gérer les exceptions vérifiées qui peuvent être levées par une
map
opération est de les encapsuler dans aCompletableFuture
. (UneOptional
est une alternative plus simple si vous n'avez pas besoin de conserver l'exception.) Ces classes sont destinées à vous permettre de représenter les opérations contingentes de manière fonctionnelle.Quelques méthodes d'assistance non triviales sont requises, mais vous pouvez arriver à un code relativement concis, tout en faisant apparaître que le résultat de votre flux dépend de la
map
réussite de l' opération. Voici à quoi ça ressemble:Cela produit la sortie suivante:
La
applyOrDie
méthode prend unFunction
qui lève une exception et le convertit en unFunction
qui renvoie un déjà terminéCompletableFuture
- soit terminé normalement avec le résultat de la fonction d'origine, soit terminé exceptionnellement avec l'exception levée.La deuxième
map
opération illustre que vous avez maintenant unStream<CompletableFuture<T>>
au lieu d'unStream<T>
.CompletableFuture
s'occupe d'exécuter cette opération uniquement si l'opération en amont a réussi. L'API rend cela explicite, mais relativement indolore.Jusqu'à ce que vous arriviez à la
collect
phase, bien sûr. C'est là que nous avons besoin d'une méthode d'assistance assez importante. Nous voulons « lever » une opération de collecte normale (dans ce cas,toList()
) « à l' intérieur » de laCompletableFuture
-cfCollector()
nous permet de le faire en utilisant unsupplier
,accumulator
,combiner
etfinisher
qui ne pas besoin de savoir quoi que ce soit au sujetCompletableFuture
.Les méthodes d'assistance peuvent être trouvées sur GitHub dans ma
MonadUtils
classe, ce qui est encore un travail en cours.la source
Une manière meilleure et plus fonctionnelle consiste probablement à encapsuler les exceptions et à les propager davantage dans le flux. Jetez un œil au type Try de Vavr par exemple.
Exemple:
OU
La 2ème implémentation évite d'encapsuler l'exception dans a
RuntimeException
.throwUnchecked
fonctionne parce que presque toujours toutes les exceptions génériques sont traitées comme non vérifiées en java.la source
J'utilise ce type d'exception de wrapping:
Il faudra gérer ces exceptions de manière statique:
Essayez-le en ligne!
Bien que l'exception soit quand même levée lors du premier
rethrow()
appel (oh, Java génériques ...), cette façon permet d'obtenir une définition statique stricte des exceptions possibles (nécessite de les déclarerthrows
). Et rieninstanceof
ou quelque chose n'est nécessaire.la source
Je pense que cette approche est la bonne:
Enroulant l'exception contrôlée à l' intérieur du
Callable
dans unUndeclaredThrowableException
(qui est le cas d'utilisation de cette exception) et déballer l' extérieur.Oui, je trouve cela moche, et je déconseille d'utiliser des lambdas dans ce cas et je retombe simplement sur une bonne vieille boucle, à moins que vous ne travailliez avec un flux parallèle et que la paralysie apporte un avantage objectif qui justifie l'illisibilité du code.
Comme beaucoup d'autres l'ont souligné, il existe des solutions à cette situation, et j'espère que l'une d'entre elles en fera une future version de Java.
la source