it-swarm.com.de

Wie kann ich CHECKED-Ausnahmen aus Java 8-Streams auslösen?

Wie kann ich CHECKED-Ausnahmen aus Java 8-Streams/Lambdas auslösen?

Mit anderen Worten, ich möchte Code so kompilieren lassen:

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

Dieser Code wird nicht kompiliert, da die Class.forName()-Methode ClassNotFoundException auslöst, die geprüft wird.

Bitte beachten Sie, dass ich die geprüfte Ausnahme NICHT innerhalb einer Laufzeitausnahme einfassen und stattdessen die umhüllte, nicht geprüfte Ausnahme auslösen möchte. Ich möchte die geprüfte Ausnahme selbst auslösen und ohne hässliche try/catches dem Stream hinzuzufügen.

246
MarcG

Mit dieser LambdaExceptionUtil-Helper-Klasse können Sie in Java-Streams alle geprüften Ausnahmen verwenden, z.

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

Hinweis Class::forName löst ClassNotFoundException aus. Dies ist checked. Der Stream selbst wirft auch ClassNotFoundException und NICHT einige ungeprüfte Wrapping-Exceptions ab.

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

}

Viele andere Beispiele zur Verwendung (nach statischem Import von 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: Die rethrow-Methoden der oben genannten LambdaExceptionUtil-Klasse können ohne Angst verwendet werden und sind in jeder Situation OK. Ein großes Dankeschön an den Benutzer @PaoloC, der bei der Lösung des letzten Problems geholfen hat: Nun fordert der Compiler Sie auf, Wurfklauseln hinzuzufügen und alles so, als könnten Sie aktiv geprüfte Ausnahmen in Java 8-Streams werfen.


NOTE 2: Die uncheck-Methoden der oben genannten LambdaExceptionUtil-Klasse sind Bonusmethoden. Sie können sie sicher aus der Klasse entfernen, wenn Sie sie nicht verwenden möchten. Wenn Sie sie verwendet haben, tun Sie dies mit Bedacht und nicht, bevor Sie die folgenden Anwendungsfälle, Vorteile/Nachteile und Einschränkungen verstanden haben:

• Sie können die uncheck-Methoden verwenden, wenn Sie eine Methode aufrufen, die buchstäblich niemals die von ihr deklarierte Ausnahme auslösen kann. Beispiel: new String (byteArr, "UTF-8") löst die UnsupportedEncodingException aus, UTF-8 wird jedoch durch die Java-Spezifikation garantiert immer vorhanden. Hier ist die Deklaration der Würfe ein Ärgernis, und jede Lösung, um sie mit minimalem Speicherplatz zum Schweigen zu bringen, ist willkommen: String text = uncheck(() -> new String(byteArr, "UTF-8"));

• Sie können die uncheck-Methoden verwenden, wenn Sie eine strikte Schnittstelle implementieren, bei der Sie keine Option zum Hinzufügen einer Throws-Deklaration haben und dennoch eine Ausnahme ausgelöst wird. Das Verpacken einer Ausnahme, nur um das Privileg zu erhalten, sie zu werfen, führt zu einem Stacktrace mit falschen Ausnahmen, die keine Informationen darüber enthalten, was tatsächlich schiefgegangen ist. Ein gutes Beispiel ist Runnable.run (), das keine geprüften Ausnahmen auslöst.

• Wenn Sie sich für die uncheck-Methoden entscheiden, wird in jedem Fall Beachten Sie diese beiden Konsequenzen, wenn Sie CHECKED-Exceptions ohne Throws-Klausel werfen: 1) Der Aufrufcode kann den Namen nicht abfangen (wenn Sie versuchen, wird der Compiler sagen: Exception wird niemals in den entsprechenden Try-Befehl geworfen Aussage). Es sprudelt und wird wahrscheinlich durch einige "catch Exception" oder "catch Throwable" in der Hauptprogrammschleife gefangen, was Sie sowieso wollen. 2) Es verstößt gegen das Prinzip der geringsten Überraschung: Es reicht nicht mehr aus, RuntimeException zu erfassen, um alle möglichen Ausnahmen einfangen zu können. Aus diesem Grund glaube ich, dass dies nicht im Rahmencode erfolgen sollte, sondern nur im Geschäftscode, den Sie vollständig kontrollieren.

158
MarcG

Die einfache Antwort auf Ihre Frage lautet: Das geht nicht, zumindest nicht direkt. Und es ist nicht deine Schuld. Oracle hat es vermasselt. Sie klammerten sich an das Konzept der geprüften Ausnahmen, vergaßen jedoch bei der Gestaltung der funktionalen Schnittstellen, der Streams, des Lambda usw. unbestimmt, die geprüften Ausnahmen zu berücksichtigen C. Martin, der angerufen hat, prüfte Ausnahmen ein gescheitertes Experiment.

Dies ist tatsächlich ein sehr großer Fehler in der API und ein kleinerer Fehler in der Sprachspezifikation.

Der Fehler in der API besteht darin, dass sie keine Möglichkeit zum Weiterleiten von geprüften Ausnahmen bietet, bei denen dies für die funktionale Programmierung wirklich sehr sinnvoll wäre. Wie ich unten zeigen werde, wäre eine solche Einrichtung leicht möglich gewesen.

Der Fehler in der Sprachspezifikation besteht darin, dass es einem Typparameter nicht erlaubt, auf eine Liste von Typen statt auf einen einzigen Typ zu schließen, solange der Typparameter nur in Situationen verwendet wird, in denen eine Liste von Typen zulässig ist (throws-Klausel).

Wir erwarten als Java-Programmierer, dass der folgende Code kompiliert werden sollte:

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

Es gibt jedoch:

[email protected]:~/playground/Java/checkedStream$ javac CheckedStream.Java 
CheckedStream.Java:13: error: incompatible thrown types ClassNotFoundException in method reference
        return names.map(Class::forName);
                         ^
1 error

Die Art und Weise, in der die funktionalen Schnittstellen definiert werden, verhindert derzeit, dass der Compiler die Ausnahme weiterleitet. Es gibt keine Deklaration, die Stream.map() mitteilt, dass if Function.apply() throws E, Stream.map() throws E ebenfalls ist.

Was fehlt, ist die Deklaration eines Typparameters für das Weiterleiten von geprüften Ausnahmen. Der folgende Code zeigt, wie ein solcher Pass-Through-Parameter tatsächlich mit der aktuellen Syntax hätte deklariert werden können. Mit Ausnahme des Sonderfalls in der markierten Zeile, bei dem es sich um eine Einschränkung handelt, wird der Code wie erwartet kompiliert und verhält sich wie erwartet.

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

Im Fall von throwSomeMore möchten wir, dass IOException vermisst wird, aber tatsächlich Exception fehlt.

Dies ist nicht perfekt, da die Typeninferenz auch bei Ausnahmen nach einem einzigen Typ zu suchen scheint. Da die Typeninferenz einen einzigen Typ benötigt, muss E in eine gemeinsame super von ClassNotFoundException und IOException aufgelöst werden, die Exception ist.

Ein Tweak bei der Definition der Typeninferenz ist erforderlich, damit der Compiler nach mehreren Typen suchen kann, wenn der Typparameter verwendet wird, wenn eine Liste von Typen zulässig ist (throws-Klausel). Der vom Compiler gemeldete Ausnahmetyp wäre dann so spezifisch wie die ursprüngliche throws-Deklaration der geprüften Ausnahmen der referenzierten Methode, nicht ein einzelner catch-all-Supertyp.

Die schlechte Nachricht ist, dass dies bedeutet, dass Oracle es vermasselt hat. Sie brechen zwar nicht den Benutzerlandcode, aber das Einfügen von Ausnahme-Typ-Parametern in die vorhandenen funktionalen Schnittstellen würde die Kompilierung aller Benutzerlandcodes, die diese Schnittstellen explizit verwenden, beeinträchtigen. Sie müssen ein paar neue Syntaxzucker erfinden, um dies zu beheben.

Noch schlimmer ist, dass dieses Thema bereits 2010 von Brian Goetz besprochen wurde https://blogs.Oracle.com/briangoetz/entry/exception_transparency_in_Java (neuer Link: http://mail.openjdk.Java .net/pipermail/lambda-dev/2010-June/001484.html ) und es scheint, dass dieses Problem einfach ignoriert wurde, daher frage ich mich, was Oracle tut.

219
Christian Hujer

Du kannst!

Erweitern Sie @marcgs UtilException und fügen Sie bei Bedarf throw E Hinzu: auf diese Weise der Compiler fordert Sie auf, Throw-Klauseln hinzuzufügen und alles ist so, als könnten Sie geprüfte Ausnahmen auslösen nativ auf Java 8's Streams.

Anleitung: Kopieren Sie einfach LambdaExceptionUtil in Ihr IDE) und verwenden Sie es dann wie unten gezeigt 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;
    }

}

Einige Tests, um Nutzung und Verhalten zu zeigen:

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

}
22
PaoloC

Das kannst du nicht sicher machen. Sie können schummeln, aber dann ist Ihr Programm kaputt und dies wird unvermeidlich dazu führen, dass Sie jemanden beißen (es sollte Sie sein, aber unser Betrug sprengt einen anderen an.) 

Hier ist ein etwas sichererer Weg (aber ich empfehle das immer noch nicht.)

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

Was Sie hier tun, ist, die Ausnahme im Lambda aufzufangen, ein Signal aus der Stream-Pipeline zu werfen, das darauf hinweist, dass die Berechnung ausnahmsweise fehlgeschlagen ist, das Signal abzufangen und auf dieses Signal einzuwirken, um die zugrunde liegende Ausnahme auszulösen. Der Schlüssel ist, dass Sie immer die synthetische Ausnahme wahrnehmen, anstatt zuzulassen, dass eine geprüfte Ausnahme ausläuft, ohne zu erklären, dass die Ausnahme ausgelöst wird. 

21
Brian Goetz

Verwenden Sie einfach eine der folgenden Optionen: NoException (mein Projekt), jOOλs Unchecked , Wurflambdas , Throwable-Schnittstellen , oder 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

Ich habe eine Bibliothek geschrieben, die die Stream-API erweitert, damit Sie geprüfte Ausnahmen auslösen können. Es verwendet Brian Goetz 'Trick.

Dein Code würde werden

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

Diese Antwort ähnelt 17, vermeidet jedoch die Definition der Wrapperausnahme:

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

Du kannst nicht.

Möglicherweise möchten Sie jedoch einen Blick auf eines meiner Projekte werfen, mit dem Sie solche "werfenden Lambdas" leichter manipulieren können.

In Ihrem Fall könnten Sie das tun:

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());

und MyException abfangen.

Das ist ein Beispiel. Ein anderes Beispiel ist, dass Sie einen Standardwert .orReturn() verwenden können.

Beachten Sie, dass dies noch eine Arbeit ist, mehr wird noch kommen. Bessere Namen, mehr Funktionen usw.

5
fge

Um die obigen Kommentare zusammenzufassen, besteht die fortschrittliche Lösung darin, einen speziellen Wrapper für ungeprüfte Funktionen mit einem Builder wie der API zu verwenden, der das Wiederherstellen, Wiederherstellen und Unterdrücken von Funktionen ermöglicht.

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());

Der folgende Code demonstriert es für Consumer-, Supplier- und Function-Schnittstellen. Es kann leicht erweitert werden. Einige öffentliche Schlüsselwörter wurden für dieses Beispiel entfernt.

Klasse Try ist der Endpunkt für Clientcode. Sichere Methoden können für jeden Funktionstyp einen eindeutigen Namen haben . CheckedConsumer , CheckedSupplier und CheckedFunction sind geprüfte Analoga von lib-Funktionen, die unabhängig von Try verwendet werden können

CheckedBuilder ist die Schnittstelle zur Behandlung von Ausnahmen in einer überprüften Funktion. orTry erlaubt die Ausführung einer anderen Funktion des gleichen Typs, wenn die vorherige fehlgeschlagen ist. handle bietet die Ausnahmebehandlung einschließlich der Ausnahmeartfilterung. Die Reihenfolge der Handler ist wichtig. Reduzieren Sie die Methoden unsafe und rethrow die letzte Ausnahme in der Ausführungskette. Reduzieren Sie die Methoden orElse und orElseGet und geben Sie einen alternativen Wert zurück, z. B. Optionale Werte, wenn alle Funktionen fehlgeschlagen sind. Es gibt auch eine Methode Unterdrückung . CheckedWrapper ist die gängige Implementierung von 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 Verwenden Sie einfach Lomboks @SneakyThrows.

Christian Hujer hat bereits ausführlich erklärt, warum das Auslösen von geprüften Ausnahmen aus einem Stream aufgrund der Einschränkungen von Java streng genommen nicht möglich ist. 

Einige andere Antworten haben Tricks erklärt, um die Einschränkungen der Sprache zu umgehen, aber dennoch die Anforderung erfüllen zu können, "die geprüfte Ausnahme selbst zu werfen, und ohne hässliche Versuche/Fänge in den Stream einzufügen" Dutzende zusätzlicher Boilerplates. 

Ich möchte eine andere Option hervorheben, die dazu führt, dass IMHO weitaus sauberer ist als alle anderen: Lomboks @SneakyThrows. Es wurde im Übrigen von anderen Antworten erwähnt, wurde aber unter vielen unnötigen Details vergraben. 

Der resultierende Code ist so einfach wie: 

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

Wir brauchten nur eine Extract Method-Refactoring-Funktion (erledigt durch die IDE) und one zusätzliche Zeile für @SneakyThrows. Die Anmerkung sorgt dafür, dass alle Boilerplate hinzugefügt werden, um sicherzustellen, dass Sie die geprüfte Ausnahme auslösen können, ohne sie in eine RuntimeException zu packen und sie nicht explizit deklarieren zu müssen. 

1
sergut

Ich verwende diese Art der Verpackungsausnahme:

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

Diese Ausnahmen müssen statisch behandelt werden:

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

Online ausprobieren!

Obwohl die Ausnahme ohnehin beim ersten Aufruf von rethrow() (oh, Java-Generics ...) ohnehin erneut ausgelöst wird, ermöglicht dies eine strikte statische Definition möglicher Ausnahmen (erfordert die Angabe in throws). Und es wird keine instanceof oder etwas benötigt.

1
Taras

Wahrscheinlich ist es eine bessere und funktionellere Methode, Ausnahmen einzubinden und im Stream weiter zu verbreiten. Schauen Sie sich beispielsweise den Try Typ von Vavr an.

Beispiel:

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

Bei der zweiten Implementierung wird vermieden, dass die Ausnahme in eine RuntimeException eingeschlossen wird. throwUnchecked funktioniert, da fast alle generischen Ausnahmen in Java als ungeprüft behandelt werden.

0

Ich stimme den obigen Kommentaren zu. Bei der Verwendung von Stream.map können Sie nur Funktionen implementieren, die keine Ausnahmen auslösen.

Sie können jedoch Ihr eigenes FunctionalInterface erstellen, das wie folgt wirft.

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

dann implementieren Sie es mit Lambdas oder Referenzen wie unten gezeigt.

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

Ich denke, dass dieser Ansatz der richtige ist:

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

Einschließen der geprüften Ausnahme in Callable in ein UndeclaredThrowableException (das ist der Anwendungsfall für diese Ausnahme), und auspacken der Ausnahme.

Ja, ich finde es hässlich und ich würde in diesem Fall von der Verwendung von Lambdas abraten und einfach auf eine gute alte Schleife zurückgreifen, es sei denn, Sie arbeiten mit einem parallelen Stream und die Parallelisierung bringt einen objektiven Vorteil, der die Unlesbarkeit des Codes rechtfertigt.

Wie viele andere darauf hingewiesen haben, gibt es Lösungen für diese Situation, und ich hoffe, dass einer von ihnen eine zukünftige Java-Version schaffen wird.

0
Paramaeleon

Sie können auch eine Wrapper-Methode schreiben, um ungeprüfte Ausnahmen zu umbrechen und sogar Wrapper mit zusätzlichen Parametern zu erweitern, die eine andere funktionale Schnittstelle darstellen (mit demselben Rückgabetyp) R). In diesem Fall können Sie eine Funktion übergeben, die im Falle von Ausnahmen ausgeführt und zurückgegeben wird . Siehe Beispiel unten:

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

Die einzige integrierte Methode zur Behandlung geprüfter Ausnahmen, die durch eine map-Operation ausgelöst werden kann, besteht darin, sie in einer CompletableFuture zu kapseln. (Eine Optional ist eine einfachere Alternative, wenn Sie die Ausnahme nicht beibehalten müssen.) Mit diesen Klassen können Sie kontingente Operationen funktional darstellen.

Es sind einige nicht triviale Hilfsmethoden erforderlich, aber Sie können zu relativ knappem Code gelangen, während Sie dennoch deutlich machen, dass das Ergebnis Ihres Streams davon abhängig ist, dass die map-Operation erfolgreich abgeschlossen wurde. So sieht es aus:

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

Dies erzeugt die folgende Ausgabe:

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

Die applyOrDie -Methode nimmt eine Function, die eine Ausnahme auslöst, und konvertiert sie in eine Function, die eine bereits abgeschlossene CompletableFuture zurückgibt - entweder normal mit dem Ergebnis der ursprünglichen Funktion oder mit Ausnahme der ausgelösten Ausnahme.

Die zweite map-Operation zeigt, dass Sie jetzt einen Stream<CompletableFuture<T>> anstatt nur einen Stream<T> haben. CompletableFuture sorgt dafür, dass diese Operation nur ausgeführt wird, wenn die Upstream-Operation erfolgreich war. Die API macht dies deutlich, aber relativ schmerzlos. 

Bis zur collect-Phase. Hier benötigen wir eine ziemlich bedeutende Hilfsmethode. Wir möchten einen normalen Erfassungsvorgang "heben" (in diesem Fall toList()) "innerhalb" der CompletableFuture - cfCollector() lässt uns dies mit einer supplier, accumulator, combiner und finisher tun, die es nicht wissen müssen irgendetwas über CompletableFuture.

Die Hilfsmethoden finden Sie auf GitHub in meiner MonadUtils -Klasse, die sich immer noch in Arbeit befindet.

0
Matt McHenry

Hier ist eine andere Ansicht oder Lösung für das ursprüngliche Problem. Hier zeige ich, dass wir die Option haben, einen Code zu schreiben, der nur eine gültige Teilmenge von Werten verarbeitet, mit der Option, Fälle zu erkennen und zu behandeln, wenn die Ausnahme ausgelöst wurde.

    @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