web-dev-qa-db-fra.com

Solution de contournement pour Java exceptions vérifiées

J'apprécie beaucoup les nouvelles fonctionnalités Java 8 sur les interfaces lambdas et les méthodes par défaut. Pourtant, je m'ennuie toujours avec des exceptions vérifiées. Par exemple, si je veux simplement lister tous les champs visibles d'un objet, je voudrais simplement écrire ceci:

    Arrays.asList(p.getClass().getFields()).forEach(
        f -> System.out.println(f.get(p))
    );

Cependant, étant donné que la méthode get peut lever une exception vérifiée, ce qui n'est pas d'accord avec le contrat d'interface Consumer, je dois intercepter cette exception et écrire le code suivant:

    Arrays.asList(p.getClass().getFields()).forEach(
            f -> {
                try {
                    System.out.println(f.get(p));
                } catch (IllegalArgumentException | IllegalAccessException ex) {
                    throw new RuntimeException(ex);
                }
            }
    );

Cependant, dans la plupart des cas, je veux juste que l'exception soit levée en tant que RuntimeException et laisser le programme gérer, ou non, l'exception sans erreurs de compilation.

Donc, je voudrais avoir votre avis sur ma solution de contournement controversée pour la gêne des exceptions vérifiées. À cette fin, j'ai créé une interface auxiliaire ConsumerCheckException<T> et une fonction utilitaire rethrow ( mise à jour en fonction de la suggestion de commentaire de Doval) comme suit:

  @FunctionalInterface
  public interface ConsumerCheckException<T>{
      void accept(T elem) throws Exception;
  }

  public class Wrappers {
      public static <T> Consumer<T> rethrow(ConsumerCheckException<T> c) {
        return elem -> {
          try {
            c.accept(elem);
          } catch (Exception ex) {
            /**
             * within sneakyThrow() we cast to the parameterized type T. 
             * In this case that type is RuntimeException. 
             * At runtime, however, the generic types have been erased, so 
             * that there is no T type anymore to cast to, so the cast
             * disappears.
             */
            Wrappers.<RuntimeException>sneakyThrow(ex);
          }
        };
      }

      /**
       * Reinier Zwitserloot who, as far as I know, had the first mention of this
       * technique in 2009 on the Java posse mailing list.
       * http://www.mail-archive.com/[email protected]/msg05984.html
       */
      public static <T extends Throwable> T sneakyThrow(Throwable t) {
          throw (T) t;
      }
  }

Et maintenant je peux simplement écrire:

    Arrays.asList(p.getClass().getFields()).forEach(
            rethrow(f -> System.out.println(f.get(p)))
    );

Je ne suis pas sûr que ce soit le meilleur idiome pour contourner les exceptions vérifiées, mais comme je l'ai expliqué, j'aimerais avoir un moyen plus pratique de réaliser mon premier exemple sans traiter les exceptions vérifiées et c'est la manière la plus simple que j'ai trouvée pour le faire.

49

Avantages, inconvénients et limites de votre technique:

  • Si le code appelant doit gérer l'exception vérifiée, vous DEVEZ l'ajouter à la clause throws de la méthode qui contient le flux. Le compilateur ne vous forcera plus à l'ajouter, il est donc plus facile de l'oublier. Par exemple:

    public void test(Object p) throws IllegalAccessException {
        Arrays.asList(p.getClass().getFields()).forEach(rethrow(f -> System.out.println(f.get(p))));
    }
    
  • Si le code appelant gère déjà l'exception vérifiée, le compilateur VOUS rappellera d'ajouter la clause throws à la déclaration de méthode qui contient le flux (si vous ne le faites pas, il dira: L'exception n'est jamais levée dans le corps de l'instruction try correspondante) .

  • Dans tous les cas, vous ne pourrez pas entourer le flux lui-même pour intercepter l'exception vérifiée DANS la méthode qui contient le flux (si vous essayez, le compilateur dira: L'exception n'est jamais levée dans le corps de l'instruction try correspondante).

  • Si vous appelez une méthode qui ne peut littéralement jamais lever l'exception qu'elle déclare, vous ne devez pas inclure la clause throws. Par exemple: une nouvelle chaîne (byteArr, "UTF-8") lève UnsupportedEncodingException, mais UTF-8 est garanti par la spécification Java pour être toujours présent. Ici, la déclaration throws est une nuisance et toute solution pour la réduire au silence avec un passe-partout minimal est la bienvenue.

  • Si vous détestez les exceptions vérifiées et que vous pensez qu'elles ne devraient jamais être ajoutées au Java pour commencer (un nombre croissant de personnes pensent de cette façon, et je ne suis PAS l'un d'eux), alors 'n'ajoutez pas l'exception vérifiée à la clause throws de la méthode qui contient le flux. L'exception vérifiée se comportera alors comme une exception UNchecked.

  • Si vous implémentez une interface stricte dans laquelle vous n'avez pas la possibilité d'ajouter une déclaration throws, et pourtant, lancer une exception est tout à fait approprié, le fait d'encapsuler une exception juste pour obtenir le privilège de la lancer entraîne une trace de pile avec des exceptions parasites qui ne fournir 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 ce cas, vous pouvez décider de ne pas ajouter l'exception vérifiée à la clause throws de la méthode qui contient le flux.

  • Dans tous les cas, si vous décidez de NE PAS ajouter (ou oubliez d'ajouter) l'exception vérifiée à la clause throws de la méthode qui contient le flux, soyez conscient de ces 2 conséquences de lever des exceptions CHECKED:

    1. Le code appelant ne pourra pas l'attraper par son nom (si vous essayez, le compilateur dira: L'exception n'est jamais levée dans le corps de l'instruction try correspondante). Il bouillonnera et sera probablement rattrapé dans la boucle du programme principal par une "exception de capture" ou "catch Throwable", qui peut être ce que vous voulez de toute façon.

    2. Il viole le principe de la moindre surprise: il ne suffira plus d'attraper RuntimeException 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.

Références:

REMARQUE: Si vous décidez d'utiliser cette technique, vous pouvez copier la classe d'assistance LambdaExceptionUtil à partir de StackOverflow: https://stackoverflow.com/questions/27644361/how-can-i-throw-checked-exceptions-from-inside-Java-8-streams . Il vous donne l'implémentation complète (Fonction, Consommateur, Fournisseur ...), avec des exemples.

16
MarcG

Dans cet exemple, peut-il vraiment échouer? Ne pense pas, mais ton cas est peut-être spécial. Si vraiment "ça ne peut pas échouer", et c'est juste un truc ennuyeux pour le compilateur, j'aime envelopper l'exception et lancer un Error avec un commentaire "ça ne peut pas arriver". Rend les choses très claires pour la maintenance. Sinon, ils se demanderont "comment cela peut-il arriver" et "qui diable gère cela?"

Ceci dans une pratique controversée donc YMMV. J'obtiendrai probablement des votes négatifs.

6
user949300

une autre version de ce code où l'exception vérifiée est juste retardée:

public class Cocoon {
         static <T extends Throwable> T forgetThrowsClause(Throwable t) throws T{
            throw (T) t;
        }

        public static <X, T extends Throwable> Consumer<X> consumer(PeskyConsumer<X,T> touchyConsumer) throws T {
            return new Consumer<X>() {
                @Override
                public void accept(X t) {
                    try {
                        touchyConsumer.accept(t);
                    } catch (Throwable exc) {
                        Cocoon.<RuntimeException>forgetThrowsClause(exc) ;
                    }

                }
            } ;
        }
// and so on for Function, and other codes from Java.util.function
}

la magie est que si vous appelez:

 myArrayList.forEach(Cocoon.consumer(MyClass::methodThatThrowsException)) ;

alors votre code sera obligé d'attraper l'exception

1
java bear

sneakyThrow est cool! Je ressens totalement ta douleur.

Si vous devez rester à Java ...

Paguro a des interfaces fonctionnelles qui encapsulent les exceptions vérifiées donc vous n'avez plus à y penser. Il comprend des collections immuables et des transformations fonctionnelles le long des lignes de transducteurs Clojure (ou Java 8 Streams) qui prennent les interfaces fonctionnelles et encapsulent les exceptions vérifiées.

Il y a aussi VAVr et The Eclipse Collections à essayer.

Sinon, utilisez Kotlin

Kotlin est compatible avec Java, n'a pas d'exceptions vérifiées, pas de primitives (enfin, pas le programmeur doit penser), un meilleur système de type, suppose l'immuabilité, etc. Même si je gère la bibliothèque Paguro ci-dessus, je convertis tout le code Java que je peux en Kotlin. Tout ce que je faisais dans Java je préfère maintenant le faire dans Kotlin .

0
GlenPeterson