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 ClassNotFoundException
wird.
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
.
throws ClassNotFoundException
in der Methodendeklaration enthalten, die den Stream enthält, damit der aufrufende Code die aktivierte Ausnahme erwartet und abfangen kann.Antworten:
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 (
throws
Klausel).Unsere Erwartung als Java-Programmierer ist, dass der folgende Code kompiliert wird:
Es gibt jedoch:
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 wennFunction.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.
Im Falle von
throwSomeMore
würden wir gerne sehen,IOException
dass wir vermisst werden, aber es fehlt tatsächlichException
.Dies ist nicht perfekt, da die Typinferenz auch bei Ausnahmen nach einem einzigen Typ zu suchen scheint. Da die Typinferenz einen einzelnen Typ
E
benötigt , muss sie in ein gemeinsamessuper
vonClassNotFoundException
und aufgelöstIOException
werdenException
.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 (
throws
Klausel). 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.
quelle
try-catch
Block im Lambda brauchen , und das macht einfach keinen Sinn. SobaldClass.forName
es in irgendeiner Weise im Lambda verwendet wird, zum Beispiel innames.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.Mit dieser
LambdaExceptionUtil
Hilfsklasse können Sie alle überprüften Ausnahmen in Java-Streams wie folgt verwenden:Note
Class::forName
wirftClassNotFoundException
, was überprüft wird . Der Stream selbst löst ebenfalls ausClassNotFoundException
und NICHT eine nicht aktivierte Ausnahme beim Umschließen.Viele andere Beispiele zur Verwendung (nach dem statischen Import
LambdaExceptionUtil
):ANMERKUNG 1: Die
rethrow
Methoden derLambdaExceptionUtil
obigen 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
uncheck
Methoden 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
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 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
uncheck
Methoden 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
uncheck
Methoden 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 fangenRuntimeException
, 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.quelle
@SuppressWarnings ("unchecked")
Compiler-Trick völlig inakzeptabel.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.)
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.
quelle
Function
usw.throws
nichts tun ; Ich bin nur Neugierig.throw w.cause;
würde den Compiler nicht beschweren, dass die Methode weder wirft noch fängtThrowable
? Es ist also wahrscheinlich, dass dort eine BesetzungIOException
benötigt wird. Wenn das Lambda mehr als eine Art von geprüfter Ausnahme auslöst, wird der Fangkörper bei einigeninstanceof
Überprüfungen (oder etwas anderem mit einem ähnlichen Zweck) etwas hässlich , um zu überprüfen, welche geprüfte Ausnahme ausgelöst wurde.Sie können!
Erweitern von @marcg 's
UtilException
und Hinzufügen, fallsthrow E
erforderlich: 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
LambdaExceptionUtil
Ihre IDE / fügen Sie sie ein und verwenden Sie sie dann wie unten gezeigtLambdaExceptionUtilTest
.Einige Tests, um Verwendung und Verhalten zu zeigen:
quelle
<Integer>
vorher hinzumap
. Tatsächlich kann der Java-Compiler denInteger
Rückgabetyp nicht ableiten . Alles andere sollte korrekt sein.Exception
.Verwenden Sie einfach eine der NoException (mein Projekt), jOOλ des Ungeprüfter , werfen-Lambda - Ausdrücke , Throwable Schnittstellen oder Faux Pas .
quelle
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
quelle
Diese Antwort ähnelt 17, vermeidet jedoch die Definition von Wrapper-Ausnahmen:
quelle
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:
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.
quelle
.orThrowChecked()
Ihrem Projekt eine Methode hinzufügen sollten , mit der die aktivierte Ausnahme selbst ausgelöst werden kann . Bitte schauen Sie sich meineUtilException
Antwort auf dieser Seite an und sehen Sie, ob Ihnen die Idee gefällt, diese dritte Möglichkeit zu Ihrem Projekt hinzuzufügen.Stream
GeräteAutoCloseable
?MyException
oben Gesagte eine ungeprüfte Ausnahme sein?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.
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.
quelle
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:
Wir brauchten nur ein
Extract Method
Refactoring (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 verpackenRuntimeException
und ohne sie explizit deklarieren zu müssen.quelle
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:
quelle
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.
quelle
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.
Implementieren Sie es dann mit Lambdas oder Referenzen, wie unten gezeigt.
quelle
Die einzige integrierte Methode zur Behandlung geprüfter Ausnahmen, die von einer
map
Operation ausgelöst werden können, besteht darin, sie in a zu kapselnCompletableFuture
. (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
map
Vorgang erfolgreich abgeschlossen wurde. So sieht es aus:Dies erzeugt die folgende Ausgabe:
Die
applyOrDie
Methode nimmt eineFunction
, die eine Ausnahme auslöst, und konvertiert sie in eineFunction
, die eine bereits abgeschlossene zurückgibtCompletableFuture
- entweder normal abgeschlossen mit dem Ergebnis der ursprünglichen Funktion oder außergewöhnlich abgeschlossen mit der ausgelösten Ausnahme.Die zweite
map
Operation zeigt, dass Sie jetzt eineStream<CompletableFuture<T>>
statt nur eine habenStream<T>
.CompletableFuture
kü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
collect
Phase kommen. Hier benötigen wir eine ziemlich wichtige Hilfsmethode. Wir wollen „Lift“ ein normaler Sammelvorgang (in diesem FalltoList()
) „innerhalb“ derCompletableFuture
-cfCollector()
lässt uns tun , dass mit einsupplier
,accumulator
,combiner
, undfinisher
die nicht brauchen , überhaupt etwas über wissenCompletableFuture
.Die Hilfsmethoden finden Sie auf GitHub in meiner
MonadUtils
Klasse, die noch in Arbeit ist.quelle
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:
ODER
Die zweite Implementierung vermeidet das Umschließen der Ausnahme in a
RuntimeException
.throwUnchecked
funktioniert, weil fast immer alle generischen Ausnahmen in Java als deaktiviert behandelt werden.quelle
Ich benutze diese Art von Wrapping-Ausnahme:
Diese Ausnahmen müssen statisch behandelt werden:
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 werdenthrows
). Und neininstanceof
oder etwas wird benötigt.quelle
Ich denke, dieser Ansatz ist der richtige:
Umschließen der aktivierten Ausnahme innerhalb von
Callable
in aUndeclaredThrowableException
(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.
quelle