Ich bin von C # auf Java umgestiegen, nachdem ich einige Empfehlungen von CodeReview erhalten hatte. Als ich mich mit LWJGL befasste, fiel mir ein, dass jeder Aufruf von Display
auf dem gleichen Thread ausgeführt werden muss, auf dem die Display.create()
Methode aufgerufen wurde. Daran erinnernd, habe ich eine Klasse zusammengestellt, die ein bisschen so aussieht.
public class LwjglDisplayWindow implements DisplayWindow {
private final static int TargetFramesPerSecond = 60;
private final Scheduler _scheduler;
public LwjglDisplayWindow(Scheduler displayScheduler, DisplayMode displayMode) throws LWJGLException {
_scheduler = displayScheduler;
Display.setDisplayMode(displayMode);
Display.create();
}
public void dispose() {
Display.destroy();
}
@Override
public int getTargetFramesPerSecond() { return TargetFramesPerSecond; }
@Override
public Future<Boolean> isClosed() {
return _scheduler.schedule(() -> Display.isCloseRequested());
}
}
Beim Schreiben dieser Klasse werden Sie feststellen, dass ich eine Methode namens isClosed()
a erstellt habe Future<Boolean>
. Dies sendet eine Funktion an meine Scheduler
Schnittstelle (die nichts anderes ist als ein Wrapper um ein ScheduledExecutorService
. Beim Schreiben der schedule
Methode auf dem ist Scheduler
mir aufgefallen, dass ich entweder ein Supplier<T>
Argument oder ein Callable<T>
Argument verwenden kann, um die übergebene Funktion darzustellen. ScheduledExecutorService
Enthält kein überschreiben, Supplier<T>
aber ich habe festgestellt, dass der Lambda-Ausdruck () -> Display.isCloseRequested()
tatsächlich mit beiden Callable<bool>
und typkompatibel ist Supplier<bool>
.
Meine Frage ist, gibt es einen Unterschied zwischen diesen beiden, semantisch oder anderweitig - und wenn ja, was ist das, damit ich mich daran halten kann?
Antworten:
Die kurze Antwort ist, dass beide funktionale Schnittstellen verwenden, aber es ist auch erwähnenswert, dass nicht alle funktionalen Schnittstellen die
@FunctionalInterface
Annotation haben müssen . Der kritische Teil des JavaDoc lautet:Und die einfachste Definition einer funktionalen Schnittstelle ist (einfach, ohne andere Ausschlüsse) nur:
Daher ist es in der Antwort von @Maciej Chalapuk auch möglich, die Anmerkung zu löschen und das gewünschte Lambda anzugeben:
Was beide
Callable
undSupplier
funktionale Schnittstellen ausmacht , ist, dass sie genau eine abstrakte Methode enthalten:Callable.call()
Supplier.get()
Da beide Methoden (im Gegensatz zum
MyInterface.myCall(int)
Beispiel) kein Argument aufnehmen , sind die formalen Parameter leer (()
).Wie Sie jetzt schließen können sollten, liegt das nur daran, dass beide abstrakten Methoden den Typ des von Ihnen verwendeten Ausdrucks zurückgeben. Sie sollten auf jeden Fall eine
Callable
bestimmte Verwendung von a verwendenScheduledExecutorService
.Weitere Erkundung (über den Rahmen der Frage hinaus)
Beide Schnittstellen stammen aus völlig unterschiedlichen Paketen und werden daher auch unterschiedlich verwendet. In Ihrem Fall sehe ich nicht, wie eine Implementierung von
Supplier<T>
verwendet wird, es sei denn, sie liefert FolgendesCallable
:Das erste
() ->
kann lose als "aSupplier
gibt ..." und das zweite als "aCallable
gibt ..." interpretiert werden .return value;
ist der Körper desCallable
Lambda, der selbst der Körper desSupplier
Lambda ist.Allerdings Verwendung in diesem erfundenen Beispiel wird etwas kompliziert, wie Sie jetzt brauchen
get()
von denSupplier
ersten bevorget()
Ihr Ergebnis aus der -tingFuture
, was wiederumcall()
IhremCallable
asynchron.quelle
Ein grundlegender Unterschied zwischen den beiden Schnittstellen besteht darin, dass Callable das Auslösen von aktivierten Ausnahmen aus der Implementierung heraus ermöglicht, während dies bei Supplier nicht der Fall ist.
Hier sind die Codefragmente aus dem JDK, die dies hervorheben:
quelle
Supplier
, wie zum Beispiel die Stream-API. Sie können Lambdas und Methodenreferenzen absolut an Methoden übergeben, die a nehmenCallable
.Wie Sie bemerken, tun sie in der Praxis dasselbe (bieten einen Wert), aber im Prinzip sollen sie verschiedene Dinge tun:
A
Callable
ist " Eine Aufgabe, die ein Ergebnis zurückgibt , während aSupplier
" ein Ergebnislieferant "ist. Mit anderen WortenCallable
ist a eine Möglichkeit, auf eine noch nicht ausgeführte ArbeitseinheitSupplier
zu verweisen , während a eine Möglichkeit ist, auf einen noch unbekannten Wert zu verweisen.Es ist möglich, dass eine
Callable
sehr kleine Arbeit erledigt und einfach einen Wert zurückgibt. Es ist auch möglich, dass eineSupplier
Menge Arbeit anfällt (z. B. Aufbau einer großen Datenstruktur). Aber im Allgemeinen ist das, worum es Ihnen bei beiden geht, ihr Hauptzweck. Zum BeispielExecutorService
arbeitet ein mitCallable
s, weil sein Hauptzweck darin besteht, Arbeitseinheiten auszuführen . Ein fauler belasteter Datenspeicher verwenden würde einSupplier
, denn es geht um kümmert wird geliefert Wert, ohne viel Sorge darüber , wie viel Arbeit , die nehmen könnte.Eine andere Möglichkeit, die Unterscheidung zu formulieren, besteht darin, dass a
Callable
Nebenwirkungen haben kann (z. B. Schreiben in eine Datei), während aSupplier
im Allgemeinen nebenwirkungsfrei sein sollte. In der Dokumentation wird dies nicht explizit erwähnt (da dies nicht erforderlich ist ), aber ich würde vorschlagen, in diesen Begriffen zu denken. Wenn die Arbeit idempotent ist, benutze aSupplier
, wenn nicht, benutze aCallable
.quelle
Beides sind normale Java-Schnittstellen ohne spezielle Semantik. Callable ist Teil der gleichzeitigen API. Der Lieferant ist Teil der neuen funktionalen Programmierschnittstelle. Sie können dank Änderungen in Java8 aus Lambda-Ausdrücken erstellt werden. @FunctionalInterface veranlasst den Compiler, die Funktionsfähigkeit der Schnittstelle zu überprüfen, und gibt einen Fehler aus, wenn dies nicht der Fall ist. Eine Schnittstelle benötigt diese Annotation jedoch nicht, um eine funktionierende Schnittstelle zu sein und von Lambdas implementiert zu werden. So kann eine Methode ein Override sein, ohne als @Override markiert zu sein, aber nicht umgekehrt.
Sie können Ihre eigenen mit Lambdas kompatiblen Schnittstellen definieren und diese mit
@FunctionalInterface
Anmerkungen dokumentieren . Die Dokumentation ist jedoch optional.quelle
IntPredicate
in Java aufgerufen .