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

287

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

Mit anderen Worten, ich möchte Code wie diesen 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()oben beschriebene Methode ausgelöst ClassNotFoundExceptionwird.

Bitte beachten Sie, dass ich die aktivierte Ausnahme NICHT in eine Laufzeitausnahme einschließen und stattdessen die umschlossene nicht aktivierte Ausnahme auslösen möchte. Ich möchte die aktivierte Ausnahme selbst auslösen , ohne dem Stream hässlich try/ hinzuzufügen catches.

MarcG
quelle
42
Die kurze Antwort lautet: Sie können nicht, ohne die Regeln für Ausnahmen zu brechen. Sie können natürlich betrügen, und andere werden Ihnen gerne zeigen, wie, aber seien Sie sich bewusst, dass dies Betrug ist und ein solcher Betrug oft zurückkommt, um Sie zu beißen. Sie sollten die Ausnahme abfangen und damit umgehen. Wenn Sie es umbrechen und anschließend die aktivierte Ausnahme erneut auslösen möchten, können Sie dies sicher tun.
Brian Goetz
35
@Brian, ich brauche keine anderen, die mir sagen, wie ich betrügen soll. Ich weiß, wie ich mich selbst betrügen soll, und habe meine Art zu betrügen in der Antwort unten angegeben, die Sie abgelehnt haben. Ich weiß, dass Sie an der Java-Diskussion beteiligt sind, bei der entschieden wurde, dass es keinen guten Weg gibt, mit geprüften Ausnahmen in Streams umzugehen. Ich finde es erstaunlich, dass Sie diese Frage von mir bemerkt haben, aber ich bin enttäuscht von Ihrer Antwort, die nur "das ist" lautet nicht gut ", gibt keinen Grund warum an, und dann fügt man noch einmal try / catches hinzu.
MarcG
22
@ Brian, ehrlich gesagt, in der Praxis wird die Hälfte von Menschen, wenn sie versuchen, Legacy-For-Statements zu refaktorisieren, in Streams konvertiert, die andere Hälfte gibt das Refactoring auf, weil niemand diese Versuche / Fänge hinzufügen möchte. Sie machen den Code viel schwieriger zu lesen, sicherlich mehr als die ursprünglichen for-Anweisungen. In meinem obigen Codebeispiel sehe ich keinen Unterschied zum externen Code, solange Sie die "Throws ClassNotFoundException" beibehalten. Könnten Sie mir bitte einige Beispiele aus dem wirklichen Leben geben, bei denen dies gegen Regeln bezüglich Ausnahmen verstößt?
MarcG
10
Das Schreiben von Wrapper-Methoden, die in ungeprüfte Ausnahmen umbrechen, behebt den Einwand "Code Clutter" und unterbricht das Typsystem nicht. Die Antwort hier, die auf einen "hinterhältigen Wurf" einer aktivierten Ausnahme zurückgreift, bricht das Typsystem, da der aufrufende Code die überprüfte Ausnahme nicht erwartet (und auch nicht abfangen darf).
Brian Goetz
14
Der Einwand gegen die Code-Unordnung wird nicht behoben, da Sie dann einen zweiten Versuch / Fang im Stream benötigen, um die ursprüngliche Ausnahme zu entpacken und erneut auszulösen. Im Gegenteil, wenn Sie die aktivierte Ausnahme auslösen, müssen Sie nur die throws ClassNotFoundExceptionin der Methodendeklaration enthalten, die den Stream enthält, damit der aufrufende Code die aktivierte Ausnahme erwartet und abfangen kann.
MarcG

Antworten:

250

Die einfache Antwort auf Ihre Frage lautet: Sie können nicht, zumindest nicht direkt.Und es ist nicht deine Schuld. Oracle hat es vermasselt.Sie halten am Konzept der geprüften Ausnahmen fest, haben jedoch uneinheitlich vergessen, die geprüften Ausnahmen beim Entwerfen der funktionalen Schnittstellen, Streams, Lambda usw. zu berücksichtigen. Das ist alles für Experten wie Robert C. Martin, die geprüfte Ausnahmen als fehlgeschlagenes Experiment bezeichnen.

Meiner Meinung nach ist dies ein großer Fehler in der API und ein kleiner Fehler in der Sprachspezifikation .

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

Der Fehler in der Sprachspezifikation besteht darin, dass ein Typparameter nicht auf eine Liste von Typen anstelle eines einzelnen Typs schließen kann, solange der Typparameter nur in Situationen verwendet wird, in denen eine Liste von Typen zulässig ist ( throwsKlausel).

Unsere Erwartung als Java-Programmierer ist, dass der folgende Code kompiliert wird:

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:

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

Die Art und Weise, wie die Funktionsschnittstellen definiert sind, verhindert derzeit, dass der Compiler die Ausnahme weiterleitet. Es gibt keine Deklaration, die besagt, Stream.map()dass wenn Function.apply() throws E,Stream.map() throws E wie gut.

Was fehlt, ist eine Deklaration eines Typparameters zum Durchlaufen geprüfter Ausnahmen. Der folgende Code zeigt, wie ein solcher Pass-Through-Parameter mit der aktuellen Syntax tatsächlich deklariert werden konnte. Mit Ausnahme des Sonderfalls in der markierten Zeile, bei dem es sich um eine unten diskutierte Grenze handelt, wird dieser Code 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 Falle von throwSomeMorewürden wir gerne sehen, IOExceptiondass wir vermisst werden, aber es fehlt tatsächlich Exception.

Dies ist nicht perfekt, da die Typinferenz auch bei Ausnahmen nach einem einzigen Typ zu suchen scheint. Da die Typinferenz einen einzelnen Typ Ebenötigt , muss sie in ein gemeinsames supervon ClassNotFoundExceptionund aufgelöst IOExceptionwerden Exception.

Die Definition der Typinferenz muss angepasst werden, damit der Compiler nach mehreren Typen sucht, wenn der Typparameter verwendet wird, wenn eine Liste von Typen zulässig ist ( throwsKlausel). Dann wäre der vom Compiler gemeldete Ausnahmetyp so spezifisch wie das Originalthrows Deklaration der geprüften Ausnahmen der referenzierten Methode, nicht ein einziger Catch-All-Supertyp.

Die schlechte Nachricht ist, dass dies bedeutet, dass Oracle es vermasselt hat. Sicherlich werden sie den Benutzerlandcode nicht beschädigen, aber die Einführung von Ausnahmetypparametern in die vorhandenen Funktionsschnittstellen würde die Kompilierung des gesamten Benutzerlandcodes, der diese Schnittstellen explizit verwendet, unterbrechen. Sie müssen einen neuen Syntaxzucker erfinden, um dies zu beheben.

Die noch schlimmere Nachricht ist, dass dieses Thema bereits 2010 von Brian Goetz diskutiert 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 ), aber ich bin informiert, dass diese Untersuchung letztendlich nicht erfolgreich war und dass es bei Oracle keine aktuellen Arbeiten gibt, von denen ich weiß, dass sie die Wechselwirkungen zwischen geprüften Ausnahmen und Lambdas abschwächen.

Christian Hujer
quelle
16
Interessant. Ich glaube, einige Leute schätzen Streams, um einfacheren parallelen Code zu ermöglichen, während andere saubereren Code zulassen. Brian Goetz kümmert sich offensichtlich mehr um Parallelität (seit er Java Concurrency in der Praxis verfasst hat), während Robert Martin sich mehr um sauberen Code kümmert (seit er das Clean Code-Buch verfasst hat). Boilerplate Try / Catches sind ein geringer Preis für Parallelität, daher ist es kein Wunder, dass Brian Goetz nicht entsetzt ist über die Probleme, geprüfte Ausnahmen in Streams zu verwenden. Kein Wunder, dass Robert Martin geprüfte Ausnahmen hasst, da sie die Unordnung vergrößern.
MarcG
5
Ich gehe davon aus, dass in einigen Jahren die Schwierigkeit, mit geprüften Ausnahmen in Streams umzugehen, zu einem dieser beiden Ergebnisse führen wird: Die Leute werden einfach aufhören, geprüfte Ausnahmen zu verwenden, ODER jeder wird anfangen, einen Hack zu verwenden, der dem sehr ähnlich ist, in dem ich gepostet habe meine UtilException Antwort. Ich hätte gewettet, dass Java-8-Streams der letzte Nagel auf dem Sarg der geprüften Ausnahmen sind, ohne dass geprüfte Ausnahmen Teil des JDK sind. Obwohl ich geprüfte Ausnahmen im Geschäftscode mag und verwende (für einige spezielle Anwendungsfälle), hätte ich alle gängigen JDK-Ausnahmen für die erweiterte Laufzeit bevorzugt.
MarcG
9
@Unihedro Das Problem bleibt, dass die Funktionsschnittstellen keine Ausnahmen weiterleiten. Ich würde den try-catchBlock im Lambda brauchen , und das macht einfach keinen Sinn. Sobald Class.forNamees in irgendeiner Weise im Lambda verwendet wird, zum Beispiel in names.forEach(Class::forName), ist das Problem da. Grundsätzlich wurden Methoden, die geprüfte Ausnahmen auslösen, aufgrund ihres (schlechten!) Designs direkt von der Teilnahme an der funktionalen Programmierung als funktionale Schnittstellen ausgeschlossen.
Christian Hujer
26
@ChristianHujer Die Exploration "Exception Transparency" war genau das - eine Exploration (eine, die aus dem BGGA-Vorschlag stammt). Bei eingehenderer Analyse stellten wir fest, dass es ein schlechtes Gleichgewicht zwischen Wert und Komplexität bietet, und es gab einige schwerwiegende Probleme (führte zu unentscheidbaren Inferenzproblemen, und "catch X" war unter anderem nicht stichhaltig). Es ist äußerst häufig, dass eine Sprachidee scheint vielversprechend - sogar "offensichtlich" - aber nach eingehenderer Untersuchung stellte sich heraus, dass es fehlerhaft war. Dies war einer dieser Fälle.
Brian Goetz
13
@BrianGoetz Gibt es einige öffentliche Informationen zu den von Ihnen erwähnten unentscheidbaren Inferenzproblemen? Ich bin neugierig und würde es gerne verstehen.
Christian Hujer
169

Mit dieser LambdaExceptionUtilHilfsklasse können Sie alle überprüften Ausnahmen in Java-Streams wie folgt verwenden:

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

Note Class::forNamewirft ClassNotFoundException, was überprüft wird . Der Stream selbst löst ebenfalls aus ClassNotFoundExceptionund NICHT eine nicht aktivierte Ausnahme beim Umschließen.

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 dem statischen Import 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");
    }    

ANMERKUNG 1: Die rethrowMethoden der LambdaExceptionUtilobigen Klasse können ohne Angst angewendet werden und sind in jeder Situation in Ordnung . Ein großes Dankeschön an den Benutzer @PaoloC, der zur Lösung des letzten Problems beigetragen hat: Jetzt werden Sie vom Compiler aufgefordert, Throw-Klauseln hinzuzufügen, und alles ist so, als könnten Sie geprüfte Ausnahmen nativ in Java 8-Streams auslösen.


ANMERKUNG 2: Die uncheckMethoden derLambdaExceptionUtil obigen Klasse sind Bonusmethoden und können sicher aus der Klasse entfernt werden, wenn Sie sie nicht verwenden möchten. Wenn Sie sie verwendet haben, tun Sie dies mit Vorsicht und nicht bevor Sie die folgenden Anwendungsfälle, Vor- / Nachteile und Einschränkungen verstanden haben:

• Sie können die uncheckMethoden 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 UnsupportedEncodingException aus, aber UTF-8 wird durch die Java-Spezifikation garantiert, dass es immer vorhanden ist. Hier ist die Wurferklärung ein Ärgernis und jede Lösung, um sie mit minimalem Boilerplate zum Schweigen zu bringen, ist willkommen:String text = uncheck(() -> new String(byteArr, "UTF-8"));

• Sie können die uncheckMethoden verwenden, wenn Sie eine strikte Schnittstelle implementieren, bei der Sie nicht die Möglichkeit haben, eine Throws-Deklaration hinzuzufügen, und dennoch das Auslösen einer Ausnahme völlig angemessen ist. Das Umschließen einer Ausnahme, nur um das Privileg zu erhalten, sie auszulösen, führt zu einer Stapelverfolgung mit falschen Ausnahmen, die keine Informationen darüber liefern, was tatsächlich schief gelaufen ist. Ein gutes Beispiel ist Runnable.run (), das keine überprüften Ausnahmen auslöst.

• Wenn Sie sich für die Verwendung der uncheckMethoden entscheiden, beachten Sie in jedem Fall die beiden Konsequenzen des Auslösens von CHECKED-Ausnahmen ohne Throws-Klausel: 1) Der aufrufende Code kann ihn nicht beim Namen abfangen (wenn Sie es versuchen, wird der Der Compiler wird sagen: Eine Ausnahme wird niemals in den Text der entsprechenden try-Anweisung geworfen. Es sprudelt und wird wahrscheinlich von einer "catch Exception" oder "catch Throwable" in der Hauptprogrammschleife abgefangen, was Sie vielleicht sowieso wollen. 2) Es verstößt gegen das Prinzip der geringsten Überraschung: Es reicht nicht mehr aus, um zu fangen RuntimeException, um garantieren zu können, dass alle möglichen Ausnahmen gefangen werden. Aus diesem Grund glaube ich, dass dies nicht im Framework-Code erfolgen sollte, sondern nur im Business-Code, den Sie vollständig kontrollieren.

MarcG
quelle
4
Ich bin der Meinung, dass diese Antwort zu Unrecht abgelehnt wurde. Der Code funktioniert. Überprüfte Ausnahmen sollen ausgelöst oder behandelt werden. Wenn Sie sie werfen möchten, behalten Sie einfach die "throw-Klausel" in der Methode bei, die den Stream enthält. Aber wenn Sie mit ihnen umgehen möchten, indem Sie sie einfach einpacken und neu werfen, dann bevorzuge ich es, den obigen Code zu verwenden, um die Ausnahmen zu "deaktivieren" und sie selbst sprudeln zu lassen. Der einzige mir bekannte Unterschied ist, dass die Bubbling-Ausnahme die RuntimeException nicht verlängert. Ich weiß, Puristen werden das nicht mögen, aber wird dies "unweigerlich zurückkommen, um jemanden zu beißen"? Scheint nicht wahrscheinlich.
MarcG
4
@Christian Hujer, um ehrlich zu sein, hat eine frühere Version abgelehnt, bevor ich die Erklärung "Vor-, Nachteile und Einschränkungen" hinzufügte. Vielleicht war es damals verdient. Sie können niemandem beibringen, wie man gegen die Regeln verstößt, ohne zumindest zu versuchen, die Konsequenzen zu verstehen und zu erklären. Der Hauptgrund, warum ich diese Frage gestellt habe, war, Feedback zu den Nachteilen meiner Antwort zu erhalten. Dieses Feedback erhielt ich nicht hier, sondern von einer anderen Frage in programmers.stackexchange. Dann bin ich hierher zurückgekommen und habe meine Antwort aktualisiert.
MarcG
16
Ich habe nur herabgestimmt, weil dies zu nicht wartbarem Code führt . Dies ist ein hässlicher Hack, wenn auch ein kluger, und ich werde diese Antwort nie nützlich finden. Dies ist wiederum ein weiteres "Nichtgebrauch" der Sprache.
Unihedron
12
@ Unihedro aber warum wurde es nicht mehr wartbar? Ich kann nicht verstehen warum. Irgendwelche Beispiele?
MarcG
2
Meiner Meinung nach ist der @SuppressWarnings ("unchecked")Compiler-Trick völlig inakzeptabel.
Thorbjørn Ravn Andersen
26

Sie können dies nicht sicher tun. Sie können betrügen, aber dann ist Ihr Programm kaputt und dies wird unweigerlich zurückkommen, um jemanden zu beißen (Sie sollten es sein, aber oft sprengt unser Betrug jemand anderen in die Luft.)

Hier ist ein etwas sicherer Weg, dies zu tun (aber ich empfehle dies 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;
}

Hier fangen Sie die Ausnahme im Lambda ab, werfen ein Signal aus der Stream-Pipeline, das angibt, dass die Berechnung ausnahmsweise fehlgeschlagen ist, fangen das Signal ab und reagieren auf dieses Signal, um die zugrunde liegende Ausnahme auszulösen. Der Schlüssel ist, dass Sie immer die synthetische Ausnahme abfangen, anstatt zuzulassen, dass eine geprüfte Ausnahme ausläuft, ohne zu deklarieren, dass diese Ausnahme ausgelöst wird.

Brian Goetz
quelle
18
Nur eine Frage; Was war die Entwurfsentscheidung, die dazu führte, dass Lambdas geprüfte Ausnahmen nicht aus ihrem Kontext heraus verbreiten konnten? Beachten Sie, dass ich verstehe, dass die funktionalen Schnittstellen wie Functionusw. throwsnichts tun ; Ich bin nur Neugierig.
27.
4
Das throw w.cause;würde den Compiler nicht beschweren, dass die Methode weder wirft noch fängt Throwable? Es ist also wahrscheinlich, dass dort eine Besetzung IOExceptionbenötigt wird. Wenn das Lambda mehr als eine Art von geprüfter Ausnahme auslöst, wird der Fangkörper bei einigen instanceofÜberprüfungen (oder etwas anderem mit einem ähnlichen Zweck) etwas hässlich , um zu überprüfen, welche geprüfte Ausnahme ausgelöst wurde.
Victor Stafusa
10
@schatten Ein Grund ist, dass Sie vergessen könnten, WE zu fangen, und dann eine seltsame Ausnahme (mit der niemand umgehen kann) auslaufen würde. (Sie könnten sagen: "Aber Sie haben die Ausnahme abgefangen, damit sie sicher ist." In diesem Spielzeugbeispiel. Aber jedes Mal, wenn ich eine Codebasis gesehen habe, die diesen Ansatz anwendet, vergisst irgendwann jemand. Die Versuchung, Ausnahmen zu ignorieren, kennt keine Grenzen.) Ein weiteres Risiko ist, dass die sichere Verwendung spezifisch für eine bestimmte Kombination (Site verwenden, Ausnahme) ist. Es lässt sich nicht gut auf mehrere Ausnahmen oder nicht homogene Verwendungen skalieren.
Brian Goetz
2
@hoodaticus Ich stimme dir zu. Bevorzugen Sie es, immer mehr zu verpacken (wie oben gezeigt, was das Risiko des "Vergessens" erhöht) oder erstellen Sie einfach 4 clevere Schnittstellen und verwenden Sie Lambdas ohne Wrapping, wie in stackoverflow.com/a/30974991/2365724 gezeigt ? Danke
PaoloC
10
Ehrlich gesagt ist diese Lösung einfach nicht praktikabel. Ich dachte, der Sinn von Bächen sei es, die Kesselplatte zu reduzieren, nicht zu erhöhen.
wvdz
24

Sie können!

Erweitern von @marcg 's UtilExceptionund Hinzufügen, falls throw Eerforderlich: Auf diese Weise fordert der Compiler Sie auf, Throw-Klauseln und alles hinzuzufügen, als könnten Sie geprüfte Ausnahmen nativ in die Streams von Java 8 werfen .

Anleitung: Kopieren Sie einfach LambdaExceptionUtilIhre IDE / fügen Sie sie ein und verwenden Sie sie 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 Verwendung 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());
    }

}
PaoloC
quelle
1
Sorry @setheron du hast recht, füge einfach <Integer>vorher hinzu map. Tatsächlich kann der Java-Compiler den IntegerRückgabetyp nicht ableiten . Alles andere sollte korrekt sein.
PaoloC
1
Das hat bei mir funktioniert. Die Antwort von MarcG wurde perfekt, indem die Behandlung der Ausnahme erzwungen wurde.
Skychan
1
Lösung für das obige Problem: Deklarieren Sie eine Variable wie diese. Consumer <ThingType> expression = rethrowConsumer ((ThingType thing) -> thing.clone ()); Verwenden Sie dann diesen Ausdruck im inneren foreach.
Skychan
1
@Skychan: Da Sie in dieser modifizierten neuen Version keine Ausnahmen mehr unterdrücken, ist das Inferenzsystem wahrscheinlich etwas schwieriger. In einigen Kommentaren unten spricht Brian Goetz über die "Ausnahmetransparenz", die zu "unentscheidbaren Inferenzproblemen" führt.
MarcG
3
Sehr schön. Das einzig unglückliche ist, dass es mit einer Methode, die mehrere geprüfte Ausnahmen auslöst, nicht perfekt funktioniert. In diesem Fall lässt Sie der Compiler einen allgemeinen Supertyp abfangen, z Exception.
wvdz
12

Verwenden Sie einfach eine der NoException (mein Projekt), jOOλ des Ungeprüfter , werfen-Lambda - Ausdrücke , 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));
Robert Važan
quelle
7

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

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

Diese Antwort ähnelt 17, vermeidet jedoch die Definition von Wrapper-Ausnahmen:

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

                //let say some functionality throws an exception
                try {
                    throw new IOException("test");
                }
                catch(Exception e) {
                    throw new RuntimeException(e);
                }
            });
        }
        catch (RuntimeException re) {
            if(re.getCause() instanceof IOException) {
                //do your logic for catching checked
            }
            else 
                throw re; // it might be that there is real runtime exception
        }
Radoslav Stoyanov
quelle
1
Das ist eine einfache und effektive Lösung.
Lin W
2
Genau das wollte Op nicht: versuchen Sie es mit Blöcken im Lambda. Darüber hinaus funktioniert es nur wie erwartet, solange kein anderer Code außerhalb des try-Blocks eine IOException in eine RuntimeException einschließt. Um dies zu vermeiden, kann eine benutzerdefinierte Wrapper-RuntimeException (definiert als private innere Klasse) verwendet werden.
Malte Hartwig
5

Du kannst nicht.

Vielleicht möchten Sie sich jedoch eines meiner Projekte ansehen, mit dem Sie solche "Wurf-Lambdas" einfacher 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 fangen MyException.

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

Beachten Sie, dass dies NOCH noch in Arbeit ist. Weitere werden folgen. Bessere Namen, mehr Funktionen usw.

fge
quelle
2
Aber wenn Sie die ursprünglich aktivierte Ausnahme auslösen möchten, müssen Sie den Versuch / Fang um den Stream hinzufügen, um ihn zu entpacken, was immer noch schrecklich ist! Ich mag die Idee, dass Sie möglicherweise eine ungeprüfte Ausnahme auslösen, wenn Sie möchten, und dass Sie möglicherweise einen Standardwert an den Stream zurückgeben, wenn Sie möchten, aber ich denke auch, dass Sie .orThrowChecked()Ihrem Projekt eine Methode hinzufügen sollten , mit der die aktivierte Ausnahme selbst ausgelöst werden kann . Bitte schauen Sie sich meine UtilExceptionAntwort auf dieser Seite an und sehen Sie, ob Ihnen die Idee gefällt, diese dritte Möglichkeit zu Ihrem Projekt hinzuzufügen.
MarcG
"Aber wenn Sie die ursprünglich aktivierte Ausnahme auslösen möchten, müssen Sie den Versuch / Fang um den Stream hinzufügen, um ihn auszupacken, was immer noch schrecklich ist!" <- ja, aber du hast keine Wahl. Lambdas können geprüfte Ausnahmen nicht aus ihrem Kontext heraus verbreiten, das ist eine Design- "Entscheidung" (ich sehe es persönlich als Fehler an, aber na ja)
fge
Was deine Idee betrifft, folge ich nicht sehr gut dem, was sie tut, sorry; Immerhin wirfst du immer noch als ungeprüft. Wie unterscheidet sich das von dem, was ich tue? (außer dass ich eine andere Schnittstelle dafür habe)
fge
Wie auch immer, Sie können gerne zu dem Projekt beitragen! Haben Sie auch bemerkt, dass StreamGeräte AutoCloseable?
27.
Lassen Sie mich Folgendes fragen: Muss das MyExceptionoben Gesagte eine ungeprüfte Ausnahme sein?
MarcG
3

Um die Kommentare über der erweiterten Lösung zusammenzufassen, wird ein spezieller Wrapper für ungeprüfte Funktionen mit einer Builder-ähnlichen API verwendet, die das Wiederherstellen, erneute Werfen und Unterdrücken 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 zeigt dies für Verbraucher-, Lieferanten- und Funktionsschnittstellen. Es kann leicht erweitert werden. Einige öffentliche Schlüsselwörter wurden für dieses Beispiel entfernt.

Class Try ist der Endpunkt für Client-Code. 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 zum Behandeln von Ausnahmen in einigen aktivierten Funktionen. Mit orTry können Sie eine andere Funktion des gleichen Typs ausführen, wenn die vorherige fehlgeschlagen ist. Handle bietet Ausnahmebehandlung einschließlich Filterung von Ausnahmetypen. Die Reihenfolge der Handler ist wichtig. Reduzieren Sie unsichere Methoden und werfen Sie die letzte Ausnahme in der Ausführungskette erneut. Reduzieren Sie die Methoden orElse und orElseGet und geben Sie alternative Werte wie optionale zurück, wenn alle Funktionen fehlgeschlagen sind. Auch gibt es Verfahren Unterdrückungs . CheckedWrapper ist die übliche 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);
    }
}
introspektiert
quelle
3

TL; DR Benutze einfach Lomboks@SneakyThrows .

Christian Hujer hat bereits ausführlich erklärt, warum das Auslösen überprüfter 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 immer noch in der Lage zu sein, die Anforderung zu erfüllen, "die geprüfte Ausnahme selbst auszulösen und ohne dem Stream hässliche Versuche / Fänge hinzuzufügen" , wobei einige von ihnen zehn zusätzliche Zeilen erfordern von Boilerplate.

Ich werde eine andere Option hervorheben, die meiner Meinung nach weitaus sauberer ist als alle anderen: die von Lombok @SneakyThrows. Es wurde im Vorbeigehen durch andere Antworten erwähnt, wurde aber unter vielen unnötigen Details etwas begraben.

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 ein Extract MethodRefactoring (von der IDE durchgeführt) und eine zusätzliche Zeile für @SneakyThrows. Die Annotation sorgt dafür, dass die gesamte Boilerplate hinzugefügt wird, um sicherzustellen, dass Sie Ihre aktivierte Ausnahme auslösen können, ohne sie in eine zu verpacken RuntimeExceptionund ohne sie explizit deklarieren zu müssen.

Sergut
quelle
4
Von der Verwendung von Lombok sollte abgeraten werden.
Dragas
2

Sie können auch eine Wrapper-Methode schreiben, um ungeprüfte Ausnahmen zu verpacken, und den Wrapper sogar um zusätzliche Parameter erweitern, die eine andere Funktionsschnittstelle darstellen (mit demselben Rückgabetyp R ). In diesem Fall können Sie eine Funktion übergeben, die bei 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;
}
Piotr Niewinski
quelle
2

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);
    }
OSGI Java
quelle
1

Ich stimme den obigen Kommentaren zu. Wenn Sie Stream.map verwenden, können Sie nur Funktionen implementieren, die keine Ausnahmen auslösen.

Sie können jedoch Ihr eigenes FunctionalInterface erstellen, das wie folgt ausgelöst wird.

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

Implementieren Sie es dann 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");

 }

}
JohnnyO
quelle
1

Die einzige integrierte Methode zur Behandlung geprüfter Ausnahmen, die von einer mapOperation ausgelöst werden können, besteht darin, sie in a zu kapseln CompletableFuture. (EinOptional ist eine einfachere Alternative, wenn Sie die Ausnahme nicht beibehalten müssen.) Mit diesen Klassen können Sie kontingente Operationen auf funktionale Weise darstellen.

Es sind einige nicht triviale Hilfsmethoden erforderlich, aber Sie können zu Code gelangen, der relativ präzise ist, und gleichzeitig deutlich machen, dass das Ergebnis Ihres Streams davon abhängt, dass der mapVorgang 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 applyOrDieMethode nimmt eine Function, die eine Ausnahme auslöst, und konvertiert sie in eine Function, die eine bereits abgeschlossene zurückgibt CompletableFuture- entweder normal abgeschlossen mit dem Ergebnis der ursprünglichen Funktion oder außergewöhnlich abgeschlossen mit der ausgelösten Ausnahme.

Die zweite mapOperation zeigt, dass Sie jetzt eine Stream<CompletableFuture<T>>statt nur eine haben Stream<T>. CompletableFuturekümmert sich darum, diese Operation nur auszuführen, wenn die Upstream-Operation erfolgreich war. Die API macht dies explizit, aber relativ schmerzlos.

Bis Sie zur collectPhase kommen. Hier benötigen wir eine ziemlich wichtige Hilfsmethode. Wir wollen „Lift“ ein normaler Sammelvorgang (in diesem Fall toList()) „innerhalb“ der CompletableFuture- cfCollector()lässt uns tun , dass mit ein supplier, accumulator, combiner, und finisherdie nicht brauchen , überhaupt etwas über wissen CompletableFuture.

Die Hilfsmethoden finden Sie auf GitHub in meiner MonadUtilsKlasse, die noch in Arbeit ist.

Matt McHenry
quelle
1

Eine bessere und funktionalere Möglichkeit besteht wahrscheinlich darin, Ausnahmen zu verpacken und im Stream weiter zu verbreiten. Schauen Sie sich zum Beispiel 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)))

ODER

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

Die zweite Implementierung vermeidet das Umschließen der Ausnahme in a RuntimeException. throwUncheckedfunktioniert, weil fast immer alle generischen Ausnahmen in Java als deaktiviert behandelt werden.

Mikhail Kholodkov
quelle
1

Ich benutze diese Art von Wrapping-Ausnahme:

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

Probieren Sie es online aus!

Obwohl die Ausnahme beim ersten rethrow()Aufruf ohnehin erneut ausgelöst wird (oh, Java-Generika ...), können Sie auf diese Weise eine strikte statische Definition möglicher Ausnahmen erhalten (muss deklariert werden throws). Und nein instanceofoder etwas wird benötigt.

Taras
quelle
-1

Ich denke, dieser Ansatz ist der richtige:

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

Umschließen der aktivierten Ausnahme innerhalb von Callablein a UndeclaredThrowableException(dies ist der Anwendungsfall für diese Ausnahme) und Auspacken außerhalb.

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

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

Matthias Ronge
quelle
1
(1) Es gibt bereits mehrere Antworten, die ein Beispiel wie dieses zeigen. Was trägt Ihre Antwort also zu den Fragen und Antworten bei, die noch nicht behandelt wurden? Wenn Sie doppelte Antworten wie diese veröffentlichen, wird die Site nur noch unübersichtlicher. (2) Das OP sagt ausdrücklich, dass sie dies nicht tun wollen. "Bitte beachten Sie, dass ich die aktivierte Ausnahme NICHT in eine Laufzeitausnahme einschließen und stattdessen die deaktivierte nicht aktivierte Ausnahme auslösen möchte."
Radiodef