Comment puis-je lancer des exceptions vérifiées depuis Java 8 streams?

Comment puis-je lancer des exceptions vérifiées depuis Java 8 streams/lambdas?

en d'autres termes, je veux faire du code comme ceci compiler:

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 compile pas, puisque la méthode Class.forName() au-dessus lance ClassNotFoundException , qui est cochée.

s'il vous Plaît noter je ne veux PAS d'envelopper l'objet d'une exception à l'intérieur d'une exception d'exécution et de lancer la enveloppé décoché exception à la place. je veux pour lancer l'exception vérifiée elle-même , et sans ajouter laid try / catches au flux.

229
demandé sur Draken 2014-12-25 08:09:38

15 réponses

la réponse simple à votre question Est: vous ne pouvez pas, du moins pas directement. et ce n'est pas ta faute. Oracle a tout gâché. ils s'accrochent à la notion d'exceptions vérifiées, mais ont, de manière incohérente, oublié de s'occuper des exceptions vérifiées lors de la conception des interfaces fonctionnelles, des flux, lambda, etc. C'est tout ce qu'ont à dire les experts comme Robert C. Martin qui appellent les exceptions vérifiées une expérience ratée.

c'est en fait un énorme bug dans le API et un bug mineur dans le spécification de langue .

le bug dans L'API est qu'il ne fournit aucune facilité pour le transfert des exceptions vérifiées où cela ferait en fait beaucoup de sens pour la programmation fonctionnelle. Comme je le montrerai ci-dessous, une telle installation aurait été facilement possible.

le bug dans le la spécification de langue est qu'elle ne permet pas à un paramètre de type d'inférer une liste de types au lieu d'un seul type tant que le paramètre de type n'est utilisé que dans des situations où une liste de types est permissible ( throws clause).

en tant que programmeurs Java, nous nous attendons à ce 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, il 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éfini empêche actuellement le compilateur de transmettre l'exception - il n'y a pas de déclaration qui indiquerait Stream.map() que si Function.apply() throws E , Stream.map() throws E aussi.

ce qui manque est une déclaration d'un paramètre de type pour passer par des exceptions vérifiées. Le code suivant montre comment un tel paramètre de type de passage aurait pu être déclaré avec la syntaxe courante. Sauf le cas particulier dans la ligne marquée, qui est une limite discutée ci-dessous, ce code 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 throwSomeMore nous aimerions voir IOException d'être raté, mais il manque Exception .

ce n'est pas parfait parce que l'inférence de type semble chercher un seul type, même dans le cas d'exceptions. Parce que l'inférence de type nécessite un seul type, E doit résoudre à un commun super de ClassNotFoundException et IOException , qui est Exception .

un ajustement à la définition de l'inférence de type est nécessaire afin que le compilateur cherche plusieurs types si le paramètre de type est utilisé où une liste de types est permise ( throws clause). Ensuite, le type d'exception déclaré par le compilateur serait aussi spécifique que la déclaration originale throws des exceptions vérifiées de la méthode référencée, pas un seul super-type catch-all.

La mauvaise nouvelle est que cette signifie que Oracle a tout fait foiré. Certes, ils ne briseront pas le code utilisateur-terre, mais l'introduction de paramètres de type exception aux interfaces fonctionnelles existantes briserait la compilation de tous les codes utilisateur-Terre qui utilisent ces interfaces explicitement. Ils vont devoir inventer une nouvelle syntaxe pour arranger ça.

la mauvaise 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 et il semble que ce problème a été simplement ignoré, donc je me demande ce que fait Oracle.

206
répondu Christian Hujer 2014-12-27 14:49:59

cette classe de helper LambdaExceptionUtil 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());

Note Class::forName jette ClassNotFoundException , qui est coché . Le flux lui-même lance aussi ClassNotFoundException , et pas une exception d'emballage 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 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");
    }    

NOTE 1: les méthodes rethrow de la classe LambdaExceptionUtil ci-dessus peuvent être utilisées sans crainte et sont utilisables dans toute situation . Un grand merci à l'utilisateur @PaoloC qui a aidé à résoudre le dernier problème: maintenant le compilateur va vous demander d'ajouter des clauses de rejet et tout est comme si vous pouviez jeter des exceptions vérifiées nativement sur Java 8 streams.


NOTE 2: le uncheck les méthodes de la classe LambdaExceptionUtil ci-dessus sont des méthodes bonus, et peuvent être retirées en toute sécurité de la classe si vous ne voulez pas les utiliser. Si vous les utilisez, faites-le avec soin, et pas avant de comprendre les cas d'utilisation, les avantages/inconvénients et les limites suivants:

• Vous pouvez utiliser le uncheck méthodes si vous appelez une méthode qui, littéralement, ne peut jamais jeter l'exception qu'il déclare. Par exemple: new String (byteArr, "UTF-8") lance 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 n'importe quelle solution pour la réduire au silence avec un boilerplate minimal est la bienvenue: String text = uncheck(() -> new String(byteArr, "UTF-8"));

"1519170920 • * vous pouvez utiliser les méthodes uncheck si vous implémentez une interface stricte où vous n'avez pas la possibilité d'ajouter une déclaration de lancers, et pourtant lancer une exception est tout à fait approprié. Emballer une exception juste pour gagner le le privilège de le lancer se traduit par une chaîne de relais avec des exceptions fictives qui ne fournissent aucune information sur ce qui s'est réellement mal passé. Un bon exemple est Praticable.run (), qui ne lance aucune exception cochée.

• Dans tous les cas, si vous décidez d'utiliser le uncheck méthodes, soyez conscient de ces 2 conséquences de lancer des exceptions vérifiées sans clause de lancer: 1) le code d'appel ne pourra pas l'attraper par son nom (si vous essayez, le compilateur dira: Exception n'est jamais jeté dans le corps de la déclaration d'essai correspondante). Il va faire des bulles et sera probablement pris dans la boucle principale du programme par une "exception" ou "catch Throwable", ce qui peut être ce que vous voulez de toute façon. 2) il viole le principe de la moindre surprise: il ne sera plus suffisant de capturer RuntimeException pour pouvoir garantir la capture de toutes les exceptions possibles. Pour cette raison, je pense que cela ne devrait pas être fait dans le code-cadre, mais seulement dans le code des affaires que vous contrôlez complètement.

146
répondu MarcG 2018-02-26 19:09:44

vous ne pouvez pas faire cela en toute sécurité. Vous pouvez tricher, mais alors votre programme est cassé et ce sera inévitablement revenir à mordre quelqu'un (il devrait être vous, mais souvent notre triche saute sur quelqu'un d'autre.)

Voici une façon un peu plus sûre de le faire (mais je ne recommande toujours pas cela.)

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 la lambda, jetant un signal hors du pipeline de flux qui indique que le calcul a échoué exceptionnellement, captant le signal, et agissant sur ce signal pour lancer l'exception sous-jacente. La clé est que vous attrapez toujours l'exception synthétique, plutôt que de permettre à une exception vérifiée de fuir sans déclarer que l'exception est jetée.

21
répondu Brian Goetz 2016-08-24 12:28:57

vous pouvez!

extension des UtilException de @marcg et ajout de throw E là où c'est nécessaire: de cette façon, le compilateur vous demandera d'ajouter des clauses de rejet et tout se passe comme si vous pouviez lancer des exceptions vérifiées nativement sur les flux de java 8.

Instructions: il suffit de copier / coller LambdaExceptionUtil dans votre IDE et puis l'utiliser comme indiqué dans le 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 test 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());
    }

}
21
répondu PaoloC 2016-10-03 10:22:14

il suffit d'utiliser l'un des NoException (mon projet), jooλ's Unchecked , shuriking-lambdas , Throwable interfaces , 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));
9
répondu Robert Važan 2017-06-16 03:44:51

j'ai écrit une bibliothèque qui s'étend le Flux d'API pour vous permettre de jeter checked exceptions. Il utilise le truc 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());
}
6
répondu Jeffrey 2015-08-10 16:49:37

cette réponse est similaire à 17, mais en évitant la définition de l'exception d'emballage:

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
        }
6
répondu Radoslav Stoyanov 2016-12-01 15:09:22

vous ne pouvez pas.

cependant, vous pouvez vouloir jeter un oeil à un de mes projets qui vous permet de manipuler plus facilement de tels "lambdas lanceurs".

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 la prise MyException .

C'est un exemple. Un autre exemple est que vous pouvez .orReturn() une valeur par défaut.

noter que il s'agit encore d'un travail en cours, d'autres sont à venir. De meilleurs noms, plus de fonctionnalités, etc.

5
répondu fge 2014-12-27 15:27:10

résumant les commentaires ci-dessus la solution avancée est d'utiliser une enveloppe spéciale pour les fonctions non vérifiées avec un constructeur comme API qui fournit la récupération, rethrowing et suppresing.

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 des consommateurs, des fournisseurs et des fonctions. Il peut être facilement étendu. Certains mots-clés publics ont été supprimés pour cet exemple.

Classe Essayer est le point de terminaison pour le code client. Les méthodes sécuritaires peut 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 "151980920

"

CheckedBuilder est l'interface pour la gestion des exceptions dans certains vérifié fonction. orTry permet d'exécuter une autre fonction de type si précédente a échoué. poignée fournit la gestion des exceptions, y compris exception type de filtrage. L'ordre des gestionnaires est important. Réduire méthodes dangereux et renvoyer renvoie dernière exception dans la chaîne d'exécution. Réduire méthodes orElse et orElseGet renvoyer la valeur de remplacement comme Option si toutes les fonctions échoué. Il y a aussi la méthode supprimer . CheckedWrapper est la mise en œuvre commune 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);
    }
}
3
répondu introspected 2016-03-23 09:48:18

j'utilise ce genre d'exception d'emballage:

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

il faudra traiter ces exceptions de façon statique:

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

bien que l'exception sera de toute façon relancée lors du premier appel rethrow() (Oh, Java generics...), ce qui permet d'obtenir une définition statique stricte des exceptions possibles (nécessite de les déclarer dans throws ). Et pas de instanceof ou quelque chose est nécessaire.

1
répondu Taras 2016-04-13 17:41:15

je suis d'accord avec les commentaires ci-dessus, en utilisant des Flux de données.map vous êtes limité à implémenter une fonction qui ne lance pas D'Exceptions.

vous pouvez cependant créer votre propre Interfacefonctionnel qui lance comme ci-dessous..

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

puis l'implémenter en utilisant Lambdas ou 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");

 }

}
0
répondu JohnnyO 2015-05-30 22:45:11

la seule façon intégrée de gérer les exceptions vérifiées qui peut être lancée par une opération map est de les encapsuler dans une CompletableFuture . (Un Optional est une alternative plus simple si vous n'avez pas besoin de préserver l'exception.) Ces classes sont destinées à vous permettre de représenter les opérations contingentes de manière fonctionnelle.

un couple de méthodes d'aide non trivial sont nécessaires, mais vous peut arriver à un code qui est relativement concis, tout en faisant ressortir que le résultat de votre flux dépend du succès de l'opération map . 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; });

produit le résultat suivant:

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

la applyOrDie méthode prend un Function qui jette une exception, et la convertit en un Function qui retourne un déjà complété CompletableFuture -- soit complété normalement avec le résultat de la fonction originale, soit complété exceptionnellement avec l'exception lancée.

la deuxième opération map illustre que vous avez maintenant un Stream<CompletableFuture<T>> au lieu d'un Stream<T> . CompletableFuture ne s'occupe de l'exécution de cette opération que si l'opération en amont a réussi. L'API rend cela explicite, mais relativement indolore.

Jusqu'à ce que vous arriviez à la phase collect , c'est-à-dire. C'est là que nous avons besoin d'une méthode d'aide assez significative. Nous voulons "soulever" une opération de collecte normale (dans ce cas, toList() ) "à l'intérieur" CompletableFuture -- cfCollector() nous permet de le faire à l'aide d'un supplier , accumulator , combiner , et finisher qui n'ont pas besoin de savoir quoi que ce soit sur CompletableFuture .

les méthodes d'aide peuvent être trouvées sur GitHub dans mon MonadUtils de la classe, qui est encore un travail en cours.

0
répondu Matt McHenry 2016-03-01 01:27:09

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;
}

envelopper l'exception contrôlée à l'intérieur du Callable dans un UndeclaredThrowableException (c'est le cas d'utilisation pour cette exception) et la déballer à l'extérieur.

Oui, je le trouve laid, et je conseillerais de ne pas utiliser lambdas dans ce cas et de simplement revenir à une bonne vieille boucle, à moins que vous travaillez avec un flux parallèle et la paralysie apporte un avantage objectif qui justifie la l'illisibilité du code.

Comme beaucoup d'autres l'ont souligné, il existe des solutions à cette situation, et j'espère que l'un d'eux ira dans une future version de Java.

0
répondu Paramaeleon 2018-01-24 08:10:20

probablement, une façon meilleure et plus fonctionnelle est d'envelopper les exceptions et de les propager plus loin dans le ruisseau. Jetez un oeil à la essayer type 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);
        }
    };
}

2ème mise en œuvre évite d'emballage l'exception d'un RuntimeException . throwUnchecked fonctionne parce que presque toujours toutes les exceptions génériques sont traitées comme sans contrôle à java.

0
répondu Mikhail Kholodkov 2018-06-18 08:56:57

TL;DR suffit d'utiliser de Lombok @SneakyThrows .

Christian Hujer a déjà expliqué en détail pourquoi lancer des exceptions vérifiées à partir d'un flux est, à strictement parler, impossible en raison des limitations de Java.

quelques autres réponses ont expliqué des trucs pour contourner les limites de la langue mais être encore en mesure de remplir l'exigence de jeter "l'exception vérifiée elle-même, et sans ajout laid try/captures au cours d'eau" , certains d'entre eux nécessitant des dizaines de lignes supplémentaires de boilerplate.

je vais mettre en évidence une autre option pour ce faire que IMHO est beaucoup plus propre que tous les autres: @SneakyThrows de Lombok . Elle a été mentionnée en passant par d'autres réponses mais était un peu enfouie 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 une Extract Method refactoring (fait par L'IDE) et une ligne supplémentaire pour @SneakyThrows . L'annotation prend soin d'ajouter tout le boilerplate pour s'assurer que vous pouvez jeter votre exception vérifiée sans l'envelopper dans un RuntimeException et sans avoir besoin de le déclarer explicitement.

0
répondu sergut 2018-10-09 12:12:45