web-dev-qa-db-fra.com

Comment puis-je générer des exceptions CHECKED depuis des flux Java 8?

Comment créer des exceptions CHECKED depuis des flux/lambdas Java 8?

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 se compile pas car la méthode Class.forName() ci-dessus renvoie ClassNotFoundException, qui est cochée.

Veuillez noter que je ne veux PAS insérer l'exception cochée dans une exception d'exécution et lancer l'exception non cochée encapsulée. Je veux lancer l'exception vérifiée elle-même , et sans ajouter laide try/catches au flux.

246
MarcG

Cette classe d'assistance 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 vérifié. Le flux lui-même jette également ClassNotFoundException, et PAS une exception d'emballage non vérifié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; }

}

De nombreux autres exemples d'utilisation (après avoir importé LambdaExceptionUtil de manière statique):

@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 OK à utiliser dans n'importe quelle situation. Un grand merci à l'utilisateur @PaoloC qui a aidé à résoudre le dernier problème: le compilateur vous demandera maintenant d'ajouter des clauses de rejet et tout se passe comme si vous pouviez générer des exceptions vérifiées de manière native sur des flux Java 8.


NOTE 2: Les méthodes uncheck de la classe LambdaExceptionUtil ci-dessus sont des méthodes bonus et peuvent être supprimées en toute sécurité de la classe si vous ne souhaitez pas les utiliser. Si vous les avez utilisés, faites-le avec précaution et pas avant de comprendre les cas d'utilisation, avantages/inconvénients et limitations suivants:

• Vous pouvez utiliser les méthodes uncheck 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 l'exception UnsupportedEncodingException, mais UTF-8 est garanti par la spécification Java pour qu'il soit toujours présent. Ici, la déclaration des lancers est une nuisance et toute solution pour la réduire au silence avec un passe-partout minimal est la bienvenue: String text = uncheck(() -> new String(byteArr, "UTF-8"));

• Vous pouvez utiliser les méthodes uncheck si vous implémentez une interface stricte sans possibilité d'ajouter une déclaration de projection, mais le lancement d'une exception est tout à fait approprié. Envelopper une exception uniquement pour avoir le privilège de l'exécuter entraîne la création d'une pile avec une exception fictive qui ne fournit aucune information sur ce qui a mal tourné. 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 méthodes uncheck, soyez conscient de ces 2 conséquences de lancer des exceptions CHECKED sans clause throws: 1) Le code appelant ne pourra pas l'attraper par son nom (si vous essayez, le compilateur dira: une exception n'est jamais lancée dans le corps de l'essai correspondant. déclaration). Il fera des bulles et sera probablement attrapé dans la boucle principale du programme par un "catch Exception" ou un "catch Throwable", ce qui peut être ce que vous voulez de toute façon. 2) Cela viole le principe de la moindre surprise: il ne suffira plus d'attraper RuntimeException pour pouvoir garantir l'interception de toutes les exceptions possibles. Pour cette raison, j'estime que cela ne devrait pas être fait dans le code cadre, mais uniquement dans le code métier que vous contrôlez complètement.

158
MarcG

La réponse simple à votre question est la suivante: vous ne pouvez pas, du moins pas directement. Et ce n'est pas ta faute. Oracle a tout gâché. Ils se cramponnent au concept des exceptions vérifiées, mais oublient de manière inconsistante de prendre en compte les exceptions vérifiées lors de la conception des interfaces fonctionnelles, des flux, de la lambda, etc. C. Martin qui appelle les exceptions vérifiées une expérience ratée.

Il s’agit en fait d’un énorme {bug} _ dans le API et d’un bogue mineur dans le spécification de la langue.

Le bogue de l'API est qu'il ne fournit aucune installation pour la transmission des exceptions vérifiées, ce qui serait vraiment très judicieux pour la programmation fonctionnelle. Comme je le démontrerai 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 cas où une liste de types est autorisée (clause throws).

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, 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 manière 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 indiquerait à Stream.map() que, si Function.apply() throws E, Stream.map() throws E également.

Ce qui manque, c'est une déclaration d'un paramètre de type permettant de passer à travers les exceptions vérifiées. Le code suivant montre comment un tel paramètre de type d'intercommunication aurait pu être déclaré avec la syntaxe actuelle. À l'exception du cas spécial dans la ligne marquée, qui est une limite discutée ci-dessous, ce code est compilé 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 que IOException soit omis, mais en réalité, il manque Exception.

Ce n'est pas parfait car l'inférence de type semble rechercher un type unique, même dans le cas d'exceptions. Comme l'inférence de type nécessite un seul type, E doit être résolu en un super commun de ClassNotFoundException et IOException, qui est Exception.

Un ajustement de la définition de l'inférence de type est nécessaire afin que le compilateur recherche plusieurs types si le paramètre type est utilisé lorsqu'une liste de types est autorisée (clause throws). Ensuite, le type d'exception signalé par le compilateur serait aussi spécifique que la déclaration throws d'origine des exceptions vérifiées de la méthode référencée, et non un seul super type fourre-tout.

La mauvaise nouvelle est que cela signifie que Oracle a tout gâché. Certes, ils ne casseront pas le code foncier utilisateur, mais l'introduction de paramètres de type exception dans les interfaces fonctionnelles existantes empêcherait la compilation de tout le code foncier utilisateur qui utilise ces interfaces explicitement. Ils devront inventer un nouveau sucre de syntaxe pour résoudre ce problème.

Pire encore, Brian Goetz a déjà abordé ce sujet 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 ) et il semble que ce problème ait été simplement ignoré. Je me demande donc ce que fait Oracle.

219
Christian Hujer

Vous pouvez!

Étendre la variable UtilException de @marcg et ajouter throw E si nécessaire: de cette façon, le compilateur vous demandera d'ajouter des clauses de rejet et tout se passera comme si vous pouviez lancer les exceptions vérifiées nativement sur les flux Java 8.

Instructions: il suffit de copier/coller LambdaExceptionUtil dans votre IDE puis de l’utiliser 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());
    }

}
21
PaoloC

Vous ne pouvez pas le faire en toute sécurité. Vous pouvez tricher, mais alors votre programme est interrompu et cela reviendra inévitablement pour mordre quelqu'un (ce devrait être vous, mais souvent notre triche explose sur quelqu'un d'autre.) 

Voici une façon un peu 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, c'est capturer l'exception dans le lambda, émettant un signal hors du pipeline de flux de données indiquant que le calcul a échoué de façon exceptionnelle, capturant le signal et agissant sur ce signal pour lever 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 cette exception est levée. 

21
Brian Goetz

Utilisez simplement l’un des NoException (mon projet), jOOλ's Unchecked , throwing-lambdas , Interfaces jetables 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));
11
Robert Važan

J'ai écrit une bibliothèque qui étend l'API Stream pour vous permettre de lancer des exceptions vérifiées. 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
Jeffrey

Cette réponse est similaire à 17, mais évite la définition d'une 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
Radoslav Stoyanov

Vous ne pouvez pas.

Cependant, vous voudrez peut-être jeter un coup d'œil à un de mes projets qui vous permet de manipuler plus facilement de tels "lancer de lambda".

Dans votre cas, vous seriez capable de faire cela:

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 .orReturn() une valeur par défaut.

Notez que c’est ENCORE un travail en cours, d’autres sont à venir. Meilleurs noms, plus de fonctionnalités, etc.

5
fge

Pour résumer 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 tel que l'API qui fournit la récupération, le renvoi 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 montre pour les interfaces consommateur, fournisseur et fonction. Il peut être facilement élargi. Certains mots clés publics ont été supprimés pour cet exemple.

Classe Try est le noeud final 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 de fonctions lib pouvant être utilisés indépendamment de Try

CheckedBuilder est l'interface de traitement des exceptions dans certaines fonctions vérifiées. ouTry permet d'exécuter une autre fonction du même type si la précédente a échoué. handle fournit la gestion des exceptions, y compris le filtrage du type d'exception. L'ordre des manutentionnaires est important. Réduire les méthodes unsafe et rethrow renvoie la dernière exception de la chaîne d'exécution. Réduire les méthodes ouElse et ouElseGet renvoient une valeur alternative similaire à celle Facultative si toutes les fonctions échouent. Il existe aussi la méthode suppress . CheckedWrapper est l'implémentation 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
introspected

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

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

Certaines autres réponses ont expliqué des astuces pour contourner les limitations du langage mais toujours en mesure de remplir l’exigence de lancer "l’exception vérifiée elle-même, et sans ajouter de vilains essais/captures au flux", des dizaines de lignes supplémentaires de passe-partout. 

Je vais souligner une autre option pour ce faire, à savoir que IMHO est beaucoup plus propre que toutes les autres: le @SneakyThrows de Lombok. Cela a été mentionné en passant par d’autres réponses mais était 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 refactorisation Extract Method (effectuée par l'EDI) et de une ligne supplémentaire pour @SneakyThrows. L’annotation prend en charge l’ajout de l’ensemble du passe-partout pour vous assurer que vous pouvez lever votre exception vérifiée sans l’envelopper dans une RuntimeException et sans avoir à la déclarer explicitement. 

1
sergut

J'utilise ce type d'exception d'emballage:

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 les exceptions soient quand même levées lors du premier appel rethrow() (oh, génériques Java ...), cette méthode permet d’obtenir une définition statique stricte des exceptions possibles (nécessite de les déclarer dans throws). Et aucun instanceof ou quelque chose n'est nécessaire.

1
Taras

Une méthode plus efficace et plus fonctionnelle consiste probablement à encapsuler les exceptions et à les propager davantage dans le flux. Jetez un coup d'œil au Essayez 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)))

OR

@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 2e mise en œuvre évite de placer l'exception dans une RuntimeException. throwUnchecked fonctionne car presque toujours toutes les exceptions génériques sont traitées comme non vérifiées en Java.

0
Mikhail Kholodkov

Je suis d'accord avec les commentaires ci-dessus. En utilisant Stream.map, vous êtes limité à l'implémentation de Function qui ne génère pas d'exceptions.

Vous pouvez cependant créer votre propre FunctionalInterface qui se présente 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");

 }

}
0
JohnnyO

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

Enveloppant l’exception vérifiée dans la Callable dans une UndeclaredThrowableException (c’est le cas d’utilisation de cette exception) et en la déroulant à l’extérieur.

Oui, je trouve cela moche, et je vous déconseille d'utiliser lambdas dans ce cas et retombez dans une bonne vieille boucle, à moins de travailler avec un flux parallèle et que la parallélisation apporte un avantage objectif qui justifie le caractère illisible 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 version future de Java.

0
Paramaeleon

Vous pouvez également écrire une méthode d'encapsulation pour envelopper les exceptions non vérifiées, et même améliorer l'enveloppe 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 transmettre une fonction qui serait exécutée et renvoyée en cas d'exception . 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;
}
0
Piotr Niewinski

Le seul moyen intégré de gérer les exceptions vérifiées pouvant être levées par une opération map consiste à les encapsuler dans un CompletableFuture . (Une Optional est une alternative plus simple si vous n'avez pas besoin de conserver l'exception.) Ces classes sont conçues pour vous permettre de représenter des opérations contingentes de manière fonctionnelle.

Quelques méthodes d'assistance non triviales sont nécessaires, mais vous pouvez obtenir un code relativement concis, tout en laissant apparaître que le résultat de votre flux dépend de l'opération map qui a abouti. 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 méthode applyOrDie prend une Function qui lève une exception et la convertit en une Function renvoyant une CompletableFuture déjà complétée - soit complétée normalement avec le résultat de la fonction d'origine, soit complétée exceptionnellement avec l'exception levée.

La deuxième opération map montre que vous avez maintenant un Stream<CompletableFuture<T>> au lieu d'un Stream<T>. CompletableFuture se charge 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 phase collect, c'est-à-dire. C’est là que nous avons besoin d’une méthode d’aide 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, combiner et finisher qui ne doivent pas savoir rien à propos de CompletableFuture.

Les méthodes auxiliaires sont disponibles sur GitHub dans ma MonadUtils class, ce qui est encore un travail en cours.

0
Matt McHenry

Voici une vue ou une solution différente pour le problème initial. Je montre ici que nous avons une option pour écrire un code qui ne traitera qu’un sous-ensemble de valeurs valides 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);
    }
0
OSGI Java