Comment puis-je lever des exceptions CHECKED depuis des flux Java 8?

287

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/ catchesau flux.

MarcG
la source
42
La réponse courte est, vous ne pouvez pas, sans enfreindre les règles concernant les exceptions. Vous pouvez tricher, bien sûr, et d'autres vous montreront volontiers comment, mais sachez que c'est de la triche et qu'une telle triche revient souvent pour vous mordre. Vous devez attraper l'exception et y faire face. Si vous souhaitez envelopper, puis renvoyer l'exception vérifiée après, vous pouvez le faire en toute sécurité.
Brian Goetz,
35
@Brian, je n'ai pas besoin que les autres me disent comment tricher, je sais comment me tromper et j'ai affiché ma façon de tricher dans la réponse ci-dessous, que vous avez rejetée. Je sais que vous êtes impliqué dans la discussion Java qui a décidé qu'il n'y avait pas de bon moyen de traiter les exceptions vérifiées dans Streams, donc je trouve étonnant que vous ayez remarqué ma question, mais je suis déçu par votre réponse qui dit simplement "c'est pas bon ", ne donne aucune raison, puis recommence un essai / captures.
MarcG
22
@Brian, Franchement, dans la pratique, lorsque les gens essaient de refactoriser les déclarations d'héritage, la moitié d'entre eux sont convertis en flux, mais l'autre moitié, ils abandonnent le refactoring, car personne ne veut ajouter ces essais / captures. Ils rendent le code beaucoup plus difficile à lire, certainement plus que les for-statement d'origine. Dans mon exemple de code ci-dessus, tant que vous maintenez la "levée ClassNotFoundException", je ne vois aucune différence avec le code extérieur. Pourriez-vous s'il vous plaît me donner des exemples concrets où cela enfreint les règles concernant les exceptions?
MarcG
10
L'écriture de méthodes d'encapsuleur qui encapsulent des exceptions non vérifiées résout l'objection de "l'encombrement du code" et ne rompt pas le système de types. La réponse ici qui recourt à un "lancer sournois" d'une exception vérifiée brise le système de types, car le code appelant ne s'attendra pas (ni ne sera autorisé à intercepter) l'exception vérifiée.
Brian Goetz
14
Il ne résout pas l'objection d'encombrement de code, car vous avez ensuite besoin d'un deuxième essai / capture autour du flux, pour déballer et renvoyer l'exception d'origine. Au contraire, si vous lancez l'exception vérifiée, vous avez juste besoin de garder la throws ClassNotFoundExceptiondans 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.
MarcG

Réponses:

250

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 ( throwsclause).

Notre attente en tant que programmeurs Java est que le code suivant soit compilé:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class CheckedStream {
    // List variant to demonstrate what we actually had before refactoring.
    public List<Class> getClasses(final List<String> names) throws ClassNotFoundException {
        final List<Class> classes = new ArrayList<>();
        for (final String name : names)
            classes.add(Class.forName(name));
        return classes;
    }

    // The Stream function which we want to compile.
    public Stream<Class> getClasses(final Stream<String> names) throws ClassNotFoundException {
        return names.map(Class::forName);
    }
}

Cependant, cela donne:

cher@armor1:~/playground/Java/checkedStream$ javac CheckedStream.java 
CheckedStream.java:13: error: incompatible thrown types ClassNotFoundException in method reference
        return names.map(Class::forName);
                         ^
1 error

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()si Function.apply() throws E, Stream.map() throws Eaussi.

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.

import java.io.IOException;
interface Function<T, R, E extends Throwable> {
    // Declare you throw E, whatever that is.
    R apply(T t) throws E;
}   

interface Stream<T> {
    // Pass through E, whatever mapper defined for E.
    <R, E extends Throwable> Stream<R> map(Function<? super T, ? extends R, E> mapper) throws E;
}   

class Main {
    public static void main(final String... args) throws ClassNotFoundException {
        final Stream<String> s = null;

        // Works: E is ClassNotFoundException.
        s.map(Class::forName);

        // Works: E is RuntimeException (probably).
        s.map(Main::convertClass);

        // Works: E is ClassNotFoundException.
        s.map(Main::throwSome);

        // Doesn't work: E is Exception.
        s.map(Main::throwSomeMore);  // error: unreported exception Exception; must be caught or declared to be thrown
    }   

    public static Class convertClass(final String s) {
        return Main.class;
    }   

    static class FooException extends ClassNotFoundException {}

    static class BarException extends ClassNotFoundException {}

    public static Class throwSome(final String s) throws FooException, BarException {
        throw new FooException();
    }   

    public static Class throwSomeMore(final String s) throws ClassNotFoundException, IOException  {
        throw new FooException();
    }   
}   

Dans le cas de throwSomeMorenous aimerions voir IOExceptionmanquer, mais il manque réellement Exception.

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, Edoit être résolue en un commun superde ClassNotFoundExceptionet IOException, ce qui est Exception.

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 ( throwsclause). Le type d'exception signalé par le compilateur serait alors aussi spécifique que la throwsdé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.

Christian Hujer
la source
16
Intéressant. Je crois que certaines personnes apprécient les flux pour permettre un code parallèle plus facile, tandis que d'autres pour permettre un code plus propre. Brian Goetz se soucie évidemment davantage du parallélisme (puisqu'il est l'auteur de Java Concurrency in Practice), tandis que Robert Martin se soucie davantage du code propre (puisqu'il est l'auteur du livre Clean Code). Les essais / captures de la chaudière sont un prix mineur à payer pour le parallélisme, il n'est donc pas étonnant que Brian Goetz ne soit pas consterné par les problèmes liés à l'utilisation d'exceptions vérifiées dans les flux. Pas étonnant non plus que Robert Martin déteste les exceptions vérifiées car elles ajoutent à l'encombrement.
MarcG
5
Je prédis que, dans quelques années, la difficulté de gérer les exceptions vérifiées dans les flux entraînera l'un de ces deux résultats: les gens cesseront simplement d'utiliser les exceptions vérifiées, OU tout le monde commencera à utiliser un hack très similaire à celui dans lequel j'ai posté ma réponse UtilException. J'aurais parié que les flux Java-8 sont le dernier clou sur le cercueil des exceptions vérifiées, n'étaient pas le fait que les exceptions vérifiées font partie du JDK. Bien que j'aime et utilise les exceptions vérifiées dans le code métier (pour certains cas d'utilisation spécifiques), j'aurais préféré toutes les exceptions JDK courantes Runtime étendu.
MarcG
9
@Unihedro Le problème demeure que les interfaces fonctionnelles ne transmettent pas d'exceptions. J'aurais besoin du try-catchbloc à l' intérieur du lambda, et cela n'a tout simplement aucun sens. Dès qu'il Class.forNameest utilisé d'une manière ou d'une autre dans le lambda, par exemple dans names.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.
Christian Hujer
26
@ChristianHujer L'exploration de la "transparence d'exception" n'était que cela - une exploration (celle qui provenait de la proposition BGGA). Après une analyse plus approfondie, nous avons constaté qu'il offrait un mauvais équilibre entre valeur et complexité, et qu'il présentait de graves problèmes (entraînant des problèmes d'inférence indécidables, et "catch X" était non fiable, entre autres). Il est extrêmement courant qu'une idée de langage semble prometteur - même "évident" - mais après une exploration plus approfondie, s'est révélé défectueux. C'était l'un de ces cas.
Brian Goetz
13
@BrianGoetz Y a-t-il des informations publiques disponibles sur les problèmes d'inférence indécidables que vous avez mentionnés? Je suis curieux et aimerais le comprendre.
Christian Hujer
169

Cette LambdaExceptionUtilclasse d'assistance vous permet d'utiliser toutes les exceptions vérifiées dans les flux Java, comme ceci:

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
      .map(rethrowFunction(Class::forName))
      .collect(Collectors.toList());

Remarque Class::forNamejette ClassNotFoundException, ce qui est vérifié . Le flux lui-même lève également ClassNotFoundException, et NON une exception non contrôlée.

public final class LambdaExceptionUtil {

@FunctionalInterface
public interface Consumer_WithExceptions<T, E extends Exception> {
    void accept(T t) throws E;
    }

@FunctionalInterface
public interface BiConsumer_WithExceptions<T, U, E extends Exception> {
    void accept(T t, U u) throws E;
    }

@FunctionalInterface
public interface Function_WithExceptions<T, R, E extends Exception> {
    R apply(T t) throws E;
    }

@FunctionalInterface
public interface Supplier_WithExceptions<T, E extends Exception> {
    T get() throws E;
    }

@FunctionalInterface
public interface Runnable_WithExceptions<E extends Exception> {
    void run() throws E;
    }

/** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */
public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
    return t -> {
        try { consumer.accept(t); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> biConsumer) throws E {
    return (t, u) -> {
        try { biConsumer.accept(t, u); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

/** .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName)) */
public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E {
    return t -> {
        try { return function.apply(t); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))), */
public static <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> function) throws E {
    return () -> {
        try { return function.get(); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** uncheck(() -> Class.forName("xxx")); */
public static void uncheck(Runnable_WithExceptions t)
    {
    try { t.run(); }
    catch (Exception exception) { throwAsUnchecked(exception); }
    }

/** uncheck(() -> Class.forName("xxx")); */
public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier)
    {
    try { return supplier.get(); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

/** uncheck(Class::forName, "xxx"); */
public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) {
    try { return function.apply(t); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

@SuppressWarnings ("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; }

}

Beaucoup d'autres exemples sur la façon de l'utiliser (après l'importation statique LambdaExceptionUtil):

@Test
public void test_Consumer_with_checked_exceptions() throws IllegalAccessException {
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(className -> System.out.println(Class.forName(className))));

    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(System.out::println));
    }

@Test
public void test_Function_with_checked_exceptions() throws ClassNotFoundException {
    List<Class> classes1
          = Stream.of("Object", "Integer", "String")
                  .map(rethrowFunction(className -> Class.forName("java.lang." + className)))
                  .collect(Collectors.toList());

    List<Class> classes2
          = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                  .map(rethrowFunction(Class::forName))
                  .collect(Collectors.toList());
    }

@Test
public void test_Supplier_with_checked_exceptions() throws ClassNotFoundException {
    Collector.of(
          rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))),
          StringJoiner::add, StringJoiner::merge, StringJoiner::toString);
    }

@Test    
public void test_uncheck_exception_thrown_by_method() {
    Class clazz1 = uncheck(() -> Class.forName("java.lang.String"));

    Class clazz2 = uncheck(Class::forName, "java.lang.String");
    }

@Test (expected = ClassNotFoundException.class)
public void test_if_correct_exception_is_still_thrown_by_method() {
    Class clazz3 = uncheck(Class::forName, "INVALID");
    }    

REMARQUE 1: Les rethrowméthodes de la LambdaExceptionUtilclasse 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 uncheckméthodes de la LambdaExceptionUtilclasse 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 uncheckmé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 uncheckmé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 uncheckmé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êcher RuntimeExceptionpour 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.

MarcG
la source
4
Je pense que cette réponse a été injustement rejetée. Le code fonctionne. Les exceptions vérifiées sont censées être levées ou traitées. Si vous souhaitez les lancer, conservez simplement la "clause throws" dans la méthode qui contient le flux. Mais si vous voulez les traiter en les encapsulant et en les renvoyant simplement, je suppose que je préfère utiliser le code ci-dessus pour "décocher" les exceptions et les laisser bouillonner par elles-mêmes. La seule différence que je sache, c'est que l'exception de propagation ne prolongera pas RuntimeException. Je sais que les puristes n'aimeront pas ça, mais est-ce que cela "reviendra inévitablement pour mordre quelqu'un"? Cela ne semble pas probable.
MarcG
4
@Christian Hujer, pour être honnête avec le downvoter, il a downvoté une version précédente avant d'ajouter l'explication "avantages, inconvénients et limitations". Alors peut-être que c'était mérité à l'époque. Vous ne pouvez pas apprendre à quelqu'un comment enfreindre les règles sans au moins essayer de comprendre et d'expliquer les conséquences. La raison principale pour laquelle j'ai posté cette question était d'obtenir des commentaires sur les inconvénients de ma réponse. J'ai fini par obtenir ces commentaires non pas ici, mais à partir d'une autre question dans programmers.stackexchange. Ensuite, je suis revenu ici et mis à jour ma réponse.
MarcG
16
J'ai rétrogradé uniquement parce que cela encourage le code non maintenable . C'est un vilain hack, bien qu'intelligent, et je ne trouverai jamais cette réponse utile. C'est, encore une fois, un autre "ne pas utiliser" de la langue.
Unihedron
12
@Unihedro mais pourquoi est-il devenu impossible à maintenir? Je ne vois pas pourquoi. Des exemples?
MarcG
2
À mon avis, la @SuppressWarnings ("unchecked")supercherie du compilateur est totalement inacceptable.
Thorbjørn Ravn Andersen
26

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.)

class WrappedException extends RuntimeException {
    Throwable cause;

    WrappedException(Throwable cause) { this.cause = cause; }
}

static WrappedException throwWrapped(Throwable t) {
    throw new WrappedException(t);
}

try 
    source.stream()
          .filter(e -> { ... try { ... } catch (IOException e) { throwWrapped(e); } ... })
          ...
}
catch (WrappedException w) {
    throw (IOException) w.cause;
}

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.

Brian Goetz
la source
18
Juste une question; quelle a été la décision de conception qui a conduit les lambdas à ne pas pouvoir propager les exceptions vérifiées hors de leur contexte? Notez que je comprends que les interfaces fonctionnelles telles que Functionetc ne font throwsrien; Je suis juste curieux.
fge
4
Cela throw w.cause;ne ferait pas se plaindre le compilateur que la méthode ne lance ni n'attrape Throwable? Il est donc probable qu'un casting IOExceptionsera 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 quelques instanceofvérifications (ou autre chose avec un objectif similaire) pour vérifier quelle exception vérifiée était levée.
Victor Stafusa
10
@schatten L'une des raisons est que vous pourriez oublier de nous attraper, puis une exception étrange (que personne ne sait comment gérer) s'échapperait. (Vous pourriez dire "mais vous avez attrapé l'exception, donc c'est sûr." Dans cet exemple de jouet. Mais chaque fois que j'ai vu une base de code adopter cette approche, quelqu'un oublie finalement. La tentation d'ignorer les exceptions ne connaît pas de limites.) Un autre risque est que son utilisation en toute sécurité est spécifique à une combinaison particulière (site d'utilisation, exception). Il ne s'adapte pas bien à de multiples exceptions ou utilisations non homogènes.
Brian Goetz
2
@hoodaticus Je suis d'accord avec vous. Cela dit, préférez-vous de plus en plus le wrapper (comme indiqué ci-dessus, augmentant le risque d'oubli) ou créez simplement 4 interfaces intelligentes et utilisez des lambdas sans wrapping, comme indiqué dans stackoverflow.com/a/30974991/2365724 ? Merci
PaoloC
10
Franchement, cette solution est tout simplement irréalisable. Je pensais que le but des ruisseaux était de réduire le passe-partout, pas de l'augmenter.
wvdz
24

Vous pouvez!

Étendre @marcg UtilExceptionet ajouter throw Esi 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 LambdaExceptionUtilvotre IDE puis utilisez-le comme indiqué ci-dessous LambdaExceptionUtilTest.

public final class LambdaExceptionUtil {

    @FunctionalInterface
    public interface Consumer_WithExceptions<T, E extends Exception> {
        void accept(T t) throws E;
    }

    @FunctionalInterface
    public interface Function_WithExceptions<T, R, E extends Exception> {
        R apply(T t) throws E;
    }

    /**
     * .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name))));
     */
    public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
        return t -> {
            try {
                consumer.accept(t);
            } catch (Exception exception) {
                throwActualException(exception);
            }
        };
    }

    /**
     * .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName))
     */
    public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E  {
        return t -> {
            try {
                return function.apply(t);
            } catch (Exception exception) {
                throwActualException(exception);
                return null;
            }
        };
    }

    @SuppressWarnings("unchecked")
    private static <E extends Exception> void throwActualException(Exception exception) throws E {
        throw (E) exception;
    }

}

Quelques tests pour montrer l'utilisation et le comportement:

public class LambdaExceptionUtilTest {

    @Test(expected = MyTestException.class)
    public void testConsumer() throws MyTestException {
        Stream.of((String)null).forEach(rethrowConsumer(s -> checkValue(s)));
    }

    private void checkValue(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
    }

    private class MyTestException extends Exception { }

    @Test
    public void testConsumerRaisingExceptionInTheMiddle() {
        MyLongAccumulator accumulator = new MyLongAccumulator();
        try {
            Stream.of(2L, 3L, 4L, null, 5L).forEach(rethrowConsumer(s -> accumulator.add(s)));
            fail();
        } catch (MyTestException e) {
            assertEquals(9L, accumulator.acc);
        }
    }

    private class MyLongAccumulator {
        private long acc = 0;
        public void add(Long value) throws MyTestException {
            if(value==null) {
                throw new MyTestException();
            }
            acc += value;
        }
    }

    @Test
    public void testFunction() throws MyTestException {
        List<Integer> sizes = Stream.of("ciao", "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
        assertEquals(2, sizes.size());
        assertEquals(4, sizes.get(0).intValue());
        assertEquals(5, sizes.get(1).intValue());
    }

    private Integer transform(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
        return value.length();
    }

    @Test(expected = MyTestException.class)
    public void testFunctionRaisingException() throws MyTestException {
        Stream.of("ciao", null, "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
    }

}
PaoloC
la source
1
Désolé @setheron vous avez raison, ajoutez juste <Integer>avant map. En fait, le compilateur java ne peut pas déduire le Integertype de retour. Tout le reste devrait être correct.
PaoloC
1
Cela a fonctionné pour moi. Cela a rendu la réponse de MarcG parfaite en imposant la gestion de l'exception.
Skychan
1
Solution au problème ci-dessus: Déclarez une variable comme celle-ci Consumer <ThingType> expression = rethrowConsumer ((ThingType thing) -> thing.clone ()); puis utilisez cette expression à l'intérieur de la foreach intérieure.
Skychan
1
@Skychan: Puisque dans cette nouvelle version modifiée vous ne supprimez plus aucune exception, c'est probablement un peu plus difficile pour le système d'inférence. Dans certains commentaires ci-dessous, Brian Goetz parle de la "transparence des exceptions" conduisant à des "problèmes d'inférence indécidables".
MarcG
3
Très agréable. La seule chose regrettable est que cela ne fonctionne pas parfaitement avec une méthode qui lève plusieurs exceptions vérifiées. Dans ce cas, le compilateur vous fera attraper un supertype commun, par exemple Exception.
wvdz
12

Utilisez simplement l'une des NoException (mon projet), les Unchecked de jOOλ , throwing-lambdas , les interfaces Throwable ou Faux Pas .

// NoException
stream.map(Exceptions.sneak().function(Class::forName));

// jOOλ
stream.map(Unchecked.function(Class::forName));

// throwing-lambdas
stream.map(Throwing.function(Class::forName).sneakyThrow());

// Throwable interfaces
stream.map(FunctionWithThrowable.aFunctionThatUnsafelyThrowsUnchecked(Class::forName));

// Faux Pas
stream.map(FauxPas.throwingFunction(Class::forName));
Robert Važan
la source
7

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

public List<Class> getClasses() throws ClassNotFoundException {     
    Stream<String> classNames = 
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String");

    return ThrowingStream.of(classNames, ClassNotFoundException.class)
               .map(Class::forName)
               .collect(Collectors.toList());
}
Jeffrey
la source
7

Cette réponse est similaire à 17 mais en évitant la définition d'exception de wrapper:

List test = new ArrayList();
        try {
            test.forEach(obj -> {

                //let say some functionality throws an exception
                try {
                    throw new IOException("test");
                }
                catch(Exception e) {
                    throw new RuntimeException(e);
                }
            });
        }
        catch (RuntimeException re) {
            if(re.getCause() instanceof IOException) {
                //do your logic for catching checked
            }
            else 
                throw re; // it might be that there is real runtime exception
        }
Radoslav Stoyanov
la source
1
C'est une solution simple et efficace.
Lin W
2
C'est exactement ce que Op ne voulait pas: essayer des blocs dans le lambda. De plus, cela ne fonctionne que prévu tant qu'aucun autre code en dehors du bloc try n'encapsule une IOException dans une RuntimeException. Pour éviter cela, une wrapper-RuntimeException personnalisée (définie comme une classe interne privée) peut être utilisée.
Malte Hartwig
5

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:

import static com.github.fge.lambdas.functions.Functions.wrap;

final ThrowingFunction<String, Class<?>> f = wrap(Class::forName);

List<Class> classes =
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .map(f.orThrow(MyException.class))
          .collect(Collectors.toList());

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.

fge
la source
2
Mais alors, si vous voulez lever l'exception vérifiée d'origine, vous devrez ajouter le try / catch autour du flux, pour le déballer, ce qui est toujours terrible! J'aime l'idée que vous POUVEZ lever une exception non vérifiée si vous le souhaitez, et que vous POUVEZ retourner une valeur par défaut au flux si vous le souhaitez, mais je pense également que vous devriez ajouter une .orThrowChecked()méthode à votre projet qui permet de lever l'exception cochée elle-même . Jetez un œil à ma UtilExceptionréponse sur cette page et voyez si vous aimez l'idée d'ajouter cette troisième possibilité à votre projet.
MarcG
"Mais alors, si vous voulez lever l'exception vérifiée d'origine, vous devrez ajouter le try / catch autour du flux, pour le déballer, ce qui est toujours terrible!" <- oui mais vous n'avez pas le choix. Les lambdas ne peuvent pas propager les exceptions vérifiées hors de leur contexte, c'est une "décision" de conception (je considère cela comme un défaut, personnellement, mais oh)
fge
Quant à votre idée, je ne suis pas très bien ce qu'elle fait, désolé; après tout, vous jetez toujours sans contrôle, alors comment est-ce différent de ce que je fais? (sauf que j'ai une interface différente pour cela)
fge
Quoi qu'il en soit, vous êtes invités à contribuer au projet! Aussi, avez-vous remarqué que cela Streamimplémente AutoCloseable?
fge
Permettez-moi de vous demander ceci: votre MyExceptionci - dessus doit-il être une exception non contrôlée?
MarcG
3

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.

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .map(Try.<String, Class<?>>safe(Class::forName)
                  .handle(System.out::println)
                  .unsafe())
          .collect(toList());

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.

final class Try {

    public static <T> CheckedBuilder<Supplier<T>, CheckedSupplier<T>, T> 
        safe(CheckedSupplier<T> supplier) {
        return new CheckedWrapper<>(supplier, 
                (current, next, handler, orResult) -> () -> {
            try { return current.get(); } catch (Exception ex) {
                handler.accept(ex);
                return next.isPresent() ? next.get().get() : orResult.apply(ex);
            }
        });
    }

    public static <T> Supplier<T> unsafe(CheckedSupplier<T> supplier) {
        return supplier;
    }

    public static <T> CheckedBuilder<Consumer<T>, CheckedConsumer<T>, Void> 
        safe(CheckedConsumer<T> consumer) {
        return new CheckedWrapper<>(consumer, 
                (current, next, handler, orResult) -> t -> {
            try { current.accept(t); } catch (Exception ex) {
                handler.accept(ex);
                if (next.isPresent()) {
                    next.get().accept(t);
                } else {
                    orResult.apply(ex);
                }
            }
        });
    }

    public static <T> Consumer<T> unsafe(CheckedConsumer<T> consumer) {
        return consumer;
    }

    public static <T, R> CheckedBuilder<Function<T, R>, CheckedFunction<T, R>, R> 
        safe(CheckedFunction<T, R> function) {
        return new CheckedWrapper<>(function, 
                (current, next, handler, orResult) -> t -> {
            try { return current.applyUnsafe(t); } catch (Exception ex) {
                handler.accept(ex);
                return next.isPresent() ? next.get().apply(t) : orResult.apply(ex);
            }
        });
    }

    public static <T, R> Function<T, R> unsafe(CheckedFunction<T, R> function) {
        return function;
    }

    @SuppressWarnings ("unchecked")
    static <T, E extends Throwable> T throwAsUnchecked(Throwable exception) throws E { 
        throw (E) exception; 
    }
}

@FunctionalInterface interface CheckedConsumer<T> extends Consumer<T> {
    void acceptUnsafe(T t) throws Exception;
    @Override default void accept(T t) {
        try { acceptUnsafe(t); } catch (Exception ex) {
            Try.throwAsUnchecked(ex);
        }
    }
}

@FunctionalInterface interface CheckedFunction<T, R> extends Function<T, R> {
    R applyUnsafe(T t) throws Exception;
    @Override default R apply(T t) {
        try { return applyUnsafe(t); } catch (Exception ex) {
            return Try.throwAsUnchecked(ex);
        }
    }
}

@FunctionalInterface interface CheckedSupplier<T> extends Supplier<T> {
    T getUnsafe() throws Exception;
    @Override default T get() {
        try { return getUnsafe(); } catch (Exception ex) {
            return Try.throwAsUnchecked(ex);
        }
    }
}

interface ReduceFunction<TSafe, TUnsafe, R> {
    TSafe wrap(TUnsafe current, Optional<TSafe> next, 
            Consumer<Throwable> handler, Function<Throwable, R> orResult);
}

interface CheckedBuilder<TSafe, TUnsafe, R> {
    CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next);

    CheckedBuilder<TSafe, TUnsafe, R> handle(Consumer<Throwable> handler);

    <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handle(
            Class<E> exceptionType, Consumer<E> handler);

    CheckedBuilder<TSafe, TUnsafe, R> handleLast(Consumer<Throwable> handler);

    <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handleLast(
            Class<E> exceptionType, Consumer<? super E> handler);

    TSafe unsafe();
    TSafe rethrow(Function<Throwable, Exception> transformer);
    TSafe suppress();
    TSafe orElse(R value);
    TSafe orElseGet(Supplier<R> valueProvider);
}

final class CheckedWrapper<TSafe, TUnsafe, R> 
        implements CheckedBuilder<TSafe, TUnsafe, R> {

    private final TUnsafe function;
    private final ReduceFunction<TSafe, TUnsafe, R> reduceFunction;

    private final CheckedWrapper<TSafe, TUnsafe, R> root;
    private CheckedWrapper<TSafe, TUnsafe, R> next;

    private Consumer<Throwable> handlers = ex -> { };
    private Consumer<Throwable> lastHandlers = ex -> { };

    CheckedWrapper(TUnsafe function, 
            ReduceFunction<TSafe, TUnsafe, R> reduceFunction) {
        this.function = function;
        this.reduceFunction = reduceFunction;
        this.root = this;
    }

    private CheckedWrapper(TUnsafe function, 
            CheckedWrapper<TSafe, TUnsafe, R> prev) {
        this.function = function;
        this.reduceFunction = prev.reduceFunction;
        this.root = prev.root;
        prev.next = this;
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next) {
        return new CheckedWrapper<>(next, this);
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> handle(
            Consumer<Throwable> handler) {
        handlers = handlers.andThen(handler);
        return this;
    }

    @Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> 
        handle(Class<E> exceptionType, Consumer<E> handler) {
        handlers = handlers.andThen(ex -> {
            if (exceptionType.isInstance(ex)) {
                handler.accept(exceptionType.cast(ex));
            }
        });
        return this;
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> handleLast(
            Consumer<Throwable> handler) {
        lastHandlers = lastHandlers.andThen(handler);
        return this;
    }

    @Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> 
        handleLast(Class<E> exceptionType, Consumer<? super E> handler) {
        lastHandlers = lastHandlers.andThen(ex -> {
            if (exceptionType.isInstance(ex)) {
                handler.accept(exceptionType.cast(ex));
            }
        });
        return this;
    }

    @Override public TSafe unsafe() {
        return root.reduce(ex -> Try.throwAsUnchecked(ex));
    }

    @Override
    public TSafe rethrow(Function<Throwable, Exception> transformer) {
        return root.reduce(ex -> Try.throwAsUnchecked(transformer.apply(ex)));
    }

    @Override public TSafe suppress() {
        return root.reduce(ex -> null);
    }

    @Override public TSafe orElse(R value) {
        return root.reduce(ex -> value);
    }

    @Override public TSafe orElseGet(Supplier<R> valueProvider) {
        Objects.requireNonNull(valueProvider);
        return root.reduce(ex -> valueProvider.get());
    }

    private TSafe reduce(Function<Throwable, R> orResult) {
        return reduceFunction.wrap(function, 
                Optional.ofNullable(next).map(p -> p.reduce(orResult)), 
                this::handle, orResult);
    }

    private void handle(Throwable ex) {
        for (CheckedWrapper<TSafe, TUnsafe, R> current = this; 
                current != null; 
                current = current.next) {
            current.handlers.accept(ex);
        }
        lastHandlers.accept(ex);
    }
}
introspection
la source
3

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:

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes =
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                .map(className -> getClass(className))
                .collect(Collectors.toList());
    return classes;
}

@SneakyThrows                                 // <= this is the only new code
private Class<?> getClass(String className) {
    return Class.forName(className);
}

Nous avions juste besoin d'une Extract Methodrefactorisation (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'envelopper RuntimeExceptionet sans avoir besoin de la déclarer explicitement.

sergut
la source
4
L'utilisation de lombok doit être déconseillée.
Dragas
2

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:

private void run() {
    List<String> list = Stream.of(1, 2, 3, 4).map(wrapper(i ->
            String.valueOf(++i / 0), i -> String.valueOf(++i))).collect(Collectors.toList());
    System.out.println(list.toString());
}

private <T, R, E extends Exception> Function<T, R> wrapper(ThrowingFunction<T, R, E> function, 
Function<T, R> onException) {
    return i -> {
        try {
            return function.apply(i);
        } catch (ArithmeticException e) {
            System.out.println("Exception: " + i);
            return onException.apply(i);
        } catch (Exception e) {
            System.out.println("Other: " + i);
            return onException.apply(i);
        }
    };
}

@FunctionalInterface
interface ThrowingFunction<T, R, E extends Exception> {
    R apply(T t) throws E;
}
Piotr Niewinski
la source
2

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.

    @Test
    public void getClasses() {

        String[] classNames = {"java.lang.Object", "java.lang.Integer", "java.lang.Foo"};
        List<Class> classes =
                Stream.of(classNames)
                        .map(className -> {
                            try {
                                return Class.forName(className);
                            } catch (ClassNotFoundException e) {
                                // log the error
                                return null;
                            }
                        })
                        .filter(c -> c != null)
                        .collect(Collectors.toList());

        if (classes.size() != classNames.length) {
            // add your error handling here if needed or process only the resulting list
            System.out.println("Did not process all class names");
        }

        classes.forEach(System.out::println);
    }
OSGI Java
la source
1

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.

@FunctionalInterface
public interface UseInstance<T, X extends Throwable> {
  void accept(T instance) throws X;
}

puis implémentez-le en utilisant Lambdas ou des références comme indiqué ci-dessous.

import java.io.FileWriter;
import java.io.IOException;

//lambda expressions and the execute around method (EAM) pattern to
//manage resources

public class FileWriterEAM  {
  private final FileWriter writer;

  private FileWriterEAM(final String fileName) throws IOException {
    writer = new FileWriter(fileName);
  }
  private void close() throws IOException {
    System.out.println("close called automatically...");
    writer.close();
  }
  public void writeStuff(final String message) throws IOException {
    writer.write(message);
  }
  //...

  public static void use(final String fileName, final UseInstance<FileWriterEAM, IOException> block) throws IOException {

    final FileWriterEAM writerEAM = new FileWriterEAM(fileName);    
    try {
      block.accept(writerEAM);
    } finally {
      writerEAM.close();
    }
  }

  public static void main(final String[] args) throws IOException {

    FileWriterEAM.use("eam.txt", writerEAM -> writerEAM.writeStuff("sweet"));

    FileWriterEAM.use("eam2.txt", writerEAM -> {
        writerEAM.writeStuff("how");
        writerEAM.writeStuff("sweet");      
      });

    FileWriterEAM.use("eam3.txt", FileWriterEAM::writeIt);     

  }


 void writeIt() throws IOException{
     this.writeStuff("How ");
     this.writeStuff("sweet ");
     this.writeStuff("it is");

 }

}
JohnnyO
la source
1

La seule façon intégrée de gérer les exceptions vérifiées qui peuvent être levées par une mapopération est de les encapsuler dans a CompletableFuture. (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 mapréussite de l' opération. Voici à quoi ça ressemble:

    CompletableFuture<List<Class<?>>> classes =
            Stream.of("java.lang.String", "java.lang.Integer", "java.lang.Double")
                  .map(MonadUtils.applyOrDie(Class::forName))
                  .map(cfc -> cfc.thenApply(Class::getSuperclass))
                  .collect(MonadUtils.cfCollector(ArrayList::new,
                                                  List::add,
                                                  (List<Class<?>> l1, List<Class<?>> l2) -> { l1.addAll(l2); return l1; },
                                                  x -> x));
    classes.thenAccept(System.out::println)
           .exceptionally(t -> { System.out.println("unable to get class: " + t); return null; });

Cela produit la sortie suivante:

[class java.lang.Object, class java.lang.Number, class java.lang.Number]

La applyOrDieméthode prend un Functionqui lève une exception et le convertit en un Functionqui 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 mapopération illustre que vous avez maintenant un Stream<CompletableFuture<T>>au lieu d'un Stream<T>. CompletableFutures'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 collectphase, 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 la CompletableFuture- cfCollector()nous permet de le faire en utilisant un supplier, accumulator, combineret finisherqui ne pas besoin de savoir quoi que ce soit au sujet CompletableFuture.

Les méthodes d'assistance peuvent être trouvées sur GitHub dans ma MonadUtilsclasse, ce qui est encore un travail en cours.

Matt McHenry
la source
1

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:

interface CheckedFunction<I, O> {
    O apply(I i) throws Exception; }

static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
    return i -> {
        try {
            return f.apply(i);
        } catch(Exception ex) {

            throw new RuntimeException(ex);
        }
    } }

fileNamesToRead.map(unchecked(file -> Files.readAllLines(file)))

OU

@SuppressWarnings("unchecked")
private static <T, E extends Exception> T throwUnchecked(Exception e) throws E {
    throw (E) e;
}

static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
    return arg -> {
        try {
            return f.apply(arg);
        } catch(Exception ex) {
            return throwUnchecked(ex);
        }
    };
}

La 2ème implémentation évite d'encapsuler l'exception dans a RuntimeException. throwUncheckedfonctionne parce que presque toujours toutes les exceptions génériques sont traitées comme non vérifiées en java.

Mikhail Kholodkov
la source
1

J'utilise ce type d'exception de wrapping:

public class CheckedExceptionWrapper extends RuntimeException {
    ...
    public <T extends Exception> CheckedExceptionWrapper rethrow() throws T {
        throw (T) getCause();
    }
}

Il faudra gérer ces exceptions de manière statique:

void method() throws IOException, ServletException {
    try { 
        list.stream().forEach(object -> {
            ...
            throw new CheckedExceptionWrapper(e);
            ...            
        });
    } catch (CheckedExceptionWrapper e){
        e.<IOException>rethrow();
        e.<ServletExcepion>rethrow();
    }
}

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éclarer throws). Et rien instanceofou quelque chose n'est nécessaire.

Taras
la source
-1

Je pense que cette approche est la bonne:

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes;
    try {
        classes = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String").map(className -> {
            try {
                return Class.forName(className);
            } catch (ClassNotFoundException e) {
                throw new UndeclaredThrowableException(e);
            }
        }).collect(Collectors.toList());
    } catch (UndeclaredThrowableException e) {
        if (e.getCause() instanceof ClassNotFoundException) {
            throw (ClassNotFoundException) e.getCause();
        } else {
            // this should never happen
            throw new IllegalStateException(e.getMessage(), e);
        }
    }
    return classes;
}

Enroulant l'exception contrôlée à l' intérieur du Callabledans un UndeclaredThrowableException(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.

Matthias Ronge
la source
1
(1) Il existe déjà plusieurs réponses montrant un exemple comme celui-ci, alors qu'est-ce que votre réponse ajoute aux questions et réponses qui ne sont pas déjà couvertes? La publication de réponses en double comme celle-ci ne fait qu'encombrer le site. (2) Le PO dit expressément qu'il ne veut pas faire cela. "Veuillez noter que je ne veux PAS encapsuler l'exception vérifiée dans une exception d'exécution et lever l'exception non cochée enveloppée à la place."
Radiodef