Ich spiele mit den neuen Lambda-Funktionen in Java 8 und habe festgestellt, dass die von Java 8 angebotenen Methoden wirklich nützlich sind. Ich frage mich jedoch, ob es eine gute Möglichkeit gibt, das folgende Szenario zu umgehen. Angenommen, Sie haben einen Objektpool-Wrapper, für den eine Art Factory erforderlich ist, um den Objektpool zu füllen, z. B. (mit java.lang.functions.Factory
):
public class JdbcConnectionPool extends ObjectPool<Connection> {
public ConnectionPool(int maxConnections, String url) {
super(new Factory<Connection>() {
@Override
public Connection make() {
try {
return DriverManager.getConnection(url);
} catch ( SQLException ex ) {
throw new RuntimeException(ex);
}
}
}, maxConnections);
}
}
Nach der Umwandlung der Funktionsschnittstelle in einen Lambda-Ausdruck sieht der obige Code folgendermaßen aus:
public class JdbcConnectionPool extends ObjectPool<Connection> {
public ConnectionPool(int maxConnections, String url) {
super(() -> {
try {
return DriverManager.getConnection(url);
} catch ( SQLException ex ) {
throw new RuntimeException(ex);
}
}, maxConnections);
}
}
Nicht so schlimm, aber die aktivierte Ausnahme java.sql.SQLException
erfordert einen try
/ catch
Block innerhalb des Lambda. In meiner Firma verwenden wir lange Zeit zwei Schnittstellen:
IOut<T>
das ist ein Äquivalent zujava.lang.functions.Factory
;- und eine spezielle Schnittstelle für die Fälle, in denen normalerweise die Weitergabe geprüfter Ausnahmen erforderlich ist :
interface IUnsafeOut<T, E extends Throwable> { T out() throws E; }
.
Beide IOut<T>
und IUnsafeOut<T>
sollen während der Migration auf Java 8 entfernt werden, es gibt jedoch keine genaue Übereinstimmung für IUnsafeOut<T, E>
. Wenn die Lambda-Ausdrücke mit geprüften Ausnahmen umgehen könnten, als wären sie nicht markiert, könnte es möglich sein, im obigen Konstruktor einfach Folgendes zu verwenden:
super(() -> DriverManager.getConnection(url), maxConnections);
Das sieht viel sauberer aus. Ich sehe, dass ich die Superklasse umschreiben kann ObjectPool
, um unsere zu akzeptieren IUnsafeOut<T>
, aber soweit ich weiß, ist Java 8 noch nicht fertig, daher könnte es einige Änderungen geben wie:
- etwas ähnliches implementieren wie
IUnsafeOut<T, E>
? (Um ehrlich zu sein, halte ich das für schmutzig - das Thema muss wählen, was akzeptiert werden soll: entwederFactory
oder "unsichere Fabrik", die keine kompatiblen Methodensignaturen haben kann) - einfach geprüfte Ausnahmen in Lambdas ignorieren, also keine Notwendigkeit in
IUnsafeOut<T, E>
Ersatz? (Warum nicht? zB eine weitere wichtige Änderung: OpenJDK, das ich verwende,javac
erfordert jetzt nicht, dass Variablen und Parameter deklariert werdenfinal
, um in einer anonymen Klasse [Funktionsschnittstelle] oder einem Lambda-Ausdruck erfasst zu werden.)
Die Frage ist also im Allgemeinen: Gibt es eine Möglichkeit, geprüfte Ausnahmen in Lambdas zu umgehen, oder ist dies in Zukunft geplant, bis Java 8 endgültig veröffentlicht wird?
Update 1
Hm-mm, soweit ich weiß, was wir derzeit haben, scheint es im Moment keinen Weg zu geben, obwohl der Artikel, auf den verwiesen wird, aus dem Jahr 2010 stammt: Brian Goetz erklärt die Ausnahmetransparenz in Java . Wenn sich in Java 8 nicht viel geändert hat, kann dies als Antwort angesehen werden. Auch Brian sagt, dass interface ExceptionalCallable<V, E extends Exception>
(was ich IUnsafeOut<T, E extends Throwable>
aus unserem Code-Erbe erwähnt habe) so ziemlich nutzlos ist, und ich stimme ihm zu.
Vermisse ich noch etwas anderes?
quelle
Antworten:
Sie sind sich nicht sicher, ob ich Ihre Frage wirklich beantworte, aber können Sie so etwas nicht einfach verwenden?
public final class SupplierUtils { private SupplierUtils() { } public static <T> Supplier<T> wrap(Callable<T> callable) { return () -> { try { return callable.call(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } }; } } public class JdbcConnectionPool extends ObjectPool<Connection> { public JdbcConnectionPool(int maxConnections, String url) { super(SupplierUtils.wrap(() -> DriverManager.getConnection(url)), maxConnections); } }
quelle
try-catch
es sich um eine Aussage und nicht um einen Ausdruck handelt. Das Umschließen von Ausnahmen ist a) ein Ärgernis, b) kaum mehr als ein Hack und umgeht das grundlegende Fehldesign.In der Lambda-Mailingliste wurde dies ausführlich besprochen . Wie Sie sehen können, schlug Brian Goetz dort vor, dass die Alternative darin besteht, einen eigenen Kombinator zu schreiben:
In jenen Tagen hieß die Consumer-Schnittstelle Block.
Ich denke, das entspricht der Antwort von JB Nizet .
Später erklärt Brian , warum dies so gestaltet wurde (der Grund des Problems)
quelle
try { iDontHaveThrowsDeclared(x -> someIOOperation(x)) } catch ( IOException ) { ... }
wird nicht kompiliert, daIOException
imcatch
Block bekannt ist, dass es sich um einen geprüften handelt und deriDontHaveThrowsDeclared
keinethrows
Klausel enthält. Also ja, es bricht.September 2015:
Sie können hierfür ET verwenden . ET ist eine kleine Java 8-Bibliothek für die Konvertierung / Übersetzung von Ausnahmen.
Mit ET können Sie schreiben:
super(() -> et.withReturningTranslation(() -> DriverManager.getConnection(url)), maxConnections);
Mehrzeilige Version:
super(() -> { return et.withReturningTranslation(() -> DriverManager.getConnection(url)); }, maxConnections);
Sie müssen lediglich eine neue
ExceptionTranslator
Instanz erstellen :Diese Instanz ist threadsicher und kann von mehreren Komponenten gemeinsam genutzt werden. Sie können spezifischere Ausnahmekonvertierungsregeln (z. B.
FooCheckedException -> BarRuntimeException
) konfigurieren, wenn Sie möchten. Wenn keine anderen Regeln verfügbar sind, werden aktivierte Ausnahmen automatisch in konvertiertRuntimeException
.(Haftungsausschluss: Ich bin der Autor von ET)
quelle
Haben Sie darüber nachgedacht, eine (nicht aktivierte) RuntimeException-Wrapper-Klasse zu verwenden, um die ursprüngliche Ausnahme aus dem Lambda-Ausdruck zu schmuggeln und die umschlossene Ausnahme dann wieder in die ursprünglich aktivierte Ausnahme umzuwandeln ?
class WrappedSqlException extends RuntimeException { static final long serialVersionUID = 20130808044800000L; public WrappedSqlException(SQLException cause) { super(cause); } public SQLException getSqlException() { return (SQLException) getCause(); } } public ConnectionPool(int maxConnections, String url) throws SQLException { try { super(() -> { try { return DriverManager.getConnection(url); } catch ( SQLException ex ) { throw new WrappedSqlException(ex); } }, maxConnections); } catch (WrappedSqlException wse) { throw wse.getSqlException(); } }
Das Erstellen einer eigenen eindeutigen Klasse sollte verhindern, dass eine andere ungeprüfte Ausnahme mit der in Ihrem Lambda verpackten Ausnahme verwechselt wird, selbst wenn die Ausnahme irgendwo in der Pipeline serialisiert wird, bevor Sie sie abfangen und erneut auslösen.
Hmm ... Das einzige, was ich hier sehe, ist, dass Sie dies in einem Konstruktor mit einem Aufruf von super () tun, was laut Gesetz die erste Aussage in Ihrem Konstruktor sein muss. Zählt
try
als vorherige Aussage? Ich habe dies (ohne den Konstruktor) in meinem eigenen Code.quelle
try/catch
Lambda-Ausdrücke explizit verwendet werden, datry/catch
Blöcke hässlich aussehen und die Lambdas auch hässlich machen. Ich denke, es gibt mindestens zwei Gründe, warum es bisher nicht so funktionieren kann: 1) die Geschichte von Java selbst; 2) Eine Methode, die mit einem Lambda-Ausdruck aufgerufen wird, der Methoden mitthrows
Aufrufen enthält, sollte wahrscheinlich "automatisch" als "throws
SomeEx " deklariert werden , selbst wenn die Methode ohne " any " deklariert istthrows
oder eine Ausnahme eines anderen Typs auslöst , anstatt dies zu sein aus dem Lambda vermehrt.IOException
nach derzeit illegalem Code wielist.forEach(p -> outputStream.write(p.hashCode()))
(write()
throwIOException
) zu suchen , aber Sie würden es wahrscheinlich nicht tun, da diesIterable.forEach()
nicht als werfende Methode deklariert istIOException
, sodass ein Compiler diese Absichten nicht kennen kann. Manchmal sind geprüfte Ausnahmen wirklich schädlich ...->
statt=>
. Ich frage mich, ob Martin Odersky während seiner Arbeit bei Sun irgendetwas davon vorgeschlagen hat. Ich hoffe, Java 9 enthält unveränderliche Versionen der meisten ihrer API-Klassen.Wir haben in meinem Unternehmen ein internes Projekt entwickelt, das uns dabei geholfen hat. Wir haben uns vor zwei Monaten entschlossen, an die Börse zu gehen.
Folgendes haben wir uns ausgedacht:
@FunctionalInterface public interface ThrowingFunction<T,R,E extends Throwable> { R apply(T arg) throws E; /** * @param <T> type * @param <E> checked exception * @return a function that accepts one argument and returns it as a value. */ static <T, E extends Exception> ThrowingFunction<T, T, E> identity() { return t -> t; } /** * @return a Function that returns the result of the given function as an Optional instance. * In case of a failure, empty Optional is returned */ static <T, R, E extends Exception> Function<T, Optional<R>> lifted(ThrowingFunction<T, R, E> f) { Objects.requireNonNull(f); return f.lift(); } static <T, R, E extends Exception> Function<T, R> unchecked(ThrowingFunction<T, R, E> f) { Objects.requireNonNull(f); return f.uncheck(); } default <V> ThrowingFunction<V, R, E> compose(final ThrowingFunction<? super V, ? extends T, E> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } default <V> ThrowingFunction<T, V, E> andThen(final ThrowingFunction<? super R, ? extends V, E> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } /** * @return a Function that returns the result as an Optional instance. In case of a failure, empty Optional is * returned */ default Function<T, Optional<R>> lift() { return t -> { try { return Optional.of(apply(t)); } catch (Throwable e) { return Optional.empty(); } }; } /** * @return a new Function instance which wraps thrown checked exception instance into a RuntimeException */ default Function<T, R> uncheck() { return t -> { try { return apply(t); } catch (final Throwable e) { throw new WrappedException(e); } }; }
}}
https://github.com/TouK/ThrowingFunction/
quelle
Das Umschließen der Ausnahme auf die beschriebene Weise funktioniert nicht. Ich habe es versucht und erhalte immer noch Compilerfehler, was tatsächlich der Spezifikation entspricht: Der Lambda-Ausdruck löst die Ausnahme aus, die mit dem Zieltyp des Methodenarguments nicht kompatibel ist: Callable; call () wirft es nicht, so dass ich den Lambda-Ausdruck nicht als Callable übergeben kann.
Im Grunde gibt es also keine Lösung: Wir sind beim Schreiben von Boilerplate festgefahren. Das einzige, was wir tun können, ist unsere Meinung zu äußern, dass dies behoben werden muss. Ich denke, die Spezifikation sollte einen Zieltyp, der auf inkompatiblen ausgelösten Ausnahmen basiert, nicht einfach blind verwerfen, sondern anschließend prüfen, ob die ausgelöste inkompatible Ausnahme im aufrufenden Bereich abgefangen oder als Auslöser deklariert wird. Und für Lambda-Ausdrücke, die nicht inline sind, schlage ich vor, dass wir sie als stillschweigend ausgelöste geprüfte Ausnahme markieren können (still in dem Sinne, dass der Compiler nicht prüfen sollte, aber die Laufzeit immer noch abfangen sollte). Markieren wir diese mit => im Gegensatz zu -> Ich weiß, dass dies keine Diskussionsseite ist, aber da dies die einzige Lösung für die Frage ist, lassen Sie sich hören und ändern Sie diese Spezifikation!
quelle
Supplier<T>
stattCallable<T>
- , die ein Surrogat Weg ist und es funktioniert für die meisten Fälle , dass ich konfrontiert mit. Ich persönlich würde das Verhalten von Lambda-Ausdrücken / Funktionsschnittstellen für geprüfte Ausnahmen ändern. Ich habe jedoch Zweifel, dass das Lambda-Dev-Team zustimmen wird, dieses Verhalten für Lambda zu ändern. Leider sind von Java geprüfte Ausnahmen absichtlich gut, in der Praxis jedoch schädlich. Dies ist ähnlich eine geprüfte Ausnahme in EinwickelnRuntimeException
innerhalbtry/catch
in jedem Verfahren.Paguro bietet funktionale Schnittstellen, die geprüfte Ausnahmen umschließen . Ich habe ein paar Monate, nachdem Sie Ihre Frage gestellt haben, angefangen, daran zu arbeiten, also waren Sie wahrscheinlich Teil der Inspiration dafür!
Sie werden feststellen, dass es in Paguro nur 4 funktionale Schnittstellen gibt, im Vergleich zu den 43 in Java 8 enthaltenen Schnittstellen. Dies liegt daran, dass Paguro Generika gegenüber Grundelementen bevorzugt.
Paguro hat Single-Pass-Transformationen in seine unveränderlichen Sammlungen eingebaut (kopiert von Clojure). Diese Transformationen entsprechen in etwa Clojure-Wandlern oder Java 8-Streams, akzeptieren jedoch funktionale Schnittstellen, die geprüfte Ausnahmen umschließen. Siehe: die Unterschiede zwischen Paguro und Java 8 Streams .
quelle
Supplier
Instanz überhaupt eine Fabrik ist? Jetzt denke ich, dass dieser schmutzige Konstruktor in der Post nur so etwas wie einen delegieren sollteIConnectionFactory
, der deklariertSQLException
, dass er geworfen werden soll, um die Absichten einer solchen Schnittstelle klar zu enthüllen, die in Zukunft mehr oder weniger leicht erweitert werden könnte.Sie können von Ihren Lambdas werfen, sie müssen nur "auf Ihre eigene Weise" deklariert werden (was sie leider nicht in Standard-JDK-Code wiederverwendbar macht, aber hey, wir tun, was wir können).
@FunctionalInterface public interface SupplierIOException { MyClass get() throws IOException; }
Oder die allgemeinere Version:
public interface ThrowingSupplier<T, E extends Exception> { T get() throws E; }
Ref hier . Es wird auch erwähnt, dass "sneakyThrow" verwendet wird, um die aktivierten Ausnahmen nicht zu deklarieren, sondern sie dennoch zu werfen. Es tut mir ein bisschen weh, vielleicht eine Option.
quelle