Przełączam się na Javę z C # po kilku zaleceniach z CodeReview. Tak więc, kiedy patrzyłem na LWJGL, przypomniałem sobie, że każde wywołanie Display
musi być wykonane w tym samym wątku, w którym wywołano Display.create()
metodę. Pamiętając o tym, przygotowałem klasę, która wygląda trochę tak.
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());
}
}
Pisząc tę klasę zauważysz, że stworzyłem metodę o nazwie, isClosed()
która zwraca a Future<Boolean>
. To wywołuje funkcyjne do mojego Scheduler
interfejsu (który nie jest niczym więcej niż owinięcie wokół ScheduledExecutorService
. Pisząc ten schedule
sposób na Scheduler
Zauważyłem, że mogę albo użyć Supplier<T>
argumentu lub do Callable<T>
argumentu do reprezentowania funkcji, która jest przekazywana w. ScheduledExecutorService
Nie zawierał przesłonięcie, Supplier<T>
ale zauważyłem, że wyrażenie lambda () -> Display.isCloseRequested()
jest w rzeczywistości zgodne z typem zarówno z, jak Callable<bool>
i Supplier<bool>
.
Moje pytanie brzmi: czy istnieje różnica między tymi dwoma, semantycznie czy inaczej - a jeśli tak, to co to jest, żebym mógł się do tego zastosować?
Odpowiedzi:
Krótka odpowiedź jest taka, że oba używają funkcjonalnych interfejsów, ale warto również zauważyć, że nie wszystkie funkcjonalne interfejsy muszą mieć
@FunctionalInterface
adnotację. Krytyczna część JavaDoc brzmi:A najprostsza definicja interfejsu funkcjonalnego to (po prostu, bez innych wyjątków):
Dlatego w odpowiedzi @Maciej Chalapuk można również upuścić adnotację i podać żądaną lambda:
Teraz interfejsy funkcjonalne
Callable
iSupplier
funkcjonalne sprawiają, że zawierają one dokładnie jedną abstrakcyjną metodę:Callable.call()
Supplier.get()
Ponieważ obie metody nie przyjmują argumentu (w przeciwieństwie do
MyInterface.myCall(int)
przykładu), parametry formalne są puste (()
).Jak powinieneś być w stanie wnioskować, to dlatego, że obie metody abstrakcyjne zwrócą typ używanego wyrażenia. Zdecydowanie powinieneś korzystać z
Callable
danego użyciaScheduledExecutorService
.Dalsza eksploracja (poza zakresem pytania)
Oba interfejsy pochodzą z zupełnie różnych pakietów , dlatego też są używane inaczej. W twoim przypadku nie widzę sposobu
Supplier<T>
użycia implementacji , chyba że zapewnia onaCallable
:Pierwszy
() ->
można luźno interpretować jako „Supplier
daje…”, a drugi jako „Callable
daje…”.return value;
jest ciałemCallable
lambda, które samo jest ciałemSupplier
lambda.Jednak użycie w tym wymyślonym przykładzie staje się nieco skomplikowane, ponieważ teraz musisz to zrobić
get()
odSupplier
pierwszego, zanimget()
odrzucisz wynik zFuture
, co z kolei spowoduje,call()
żeCallable
asynchronicznie.źródło
Jedną podstawową różnicą między dwoma interfejsami jest to, że Callable pozwala na rzucanie sprawdzonych wyjątków z poziomu jego implementacji, podczas gdy Dostawca nie.
Oto fragmenty kodu z JDK podkreślające to -
źródło
Supplier
, takich jak interfejs API strumienia. Absolutnie możesz przekazać lambdas i referencje metod do metod, które wymagająCallable
.Jak zauważasz, w praktyce robią to samo (zapewniają pewną wartość), jednak w zasadzie mają na celu różne rzeczy:
A
Callable
to „ Zadanie, które zwraca wynik , podczas gdy aSupplier
jest„ dostawcą wyników ”. Innymi słowy, aCallable
jest sposobem na odniesienie się do jeszcze niewykonanej jednostki pracy, a aSupplier
jest sposobem na odniesienie do jeszcze nieznanej wartości.Możliwe, że
Callable
wykona bardzo mało pracy i po prostu zwróci wartość. Możliwe jest również, żeSupplier
może wykonać sporo pracy (np. Zbudować dużą strukturę danych). Ale ogólnie mówiąc, tym, na czym ci zależy, jest ich główny cel. Na przykład anExecutorService
działa zCallable
s, ponieważ jego głównym celem jest wykonywanie jednostek pracy. Leniwie załadowany magazyn danych skorzystałby zSupplier
, ponieważ zależy mu na dostarczeniu wartości, bez większego obawy o to, ile pracy może to zająć.Innym sposobem sformułowania tego rozróżnienia jest to, że a
Callable
może mieć skutki uboczne (np. Zapis do pliku), podczas gdy aSupplier
ogólnie powinno być wolne od skutków ubocznych. Dokumentacja wyraźnie nie wspomina o tym (ponieważ nie jest to wymóg ), ale sugerowałbym myślenie w tych kategoriach. Jeśli praca jest idempotentna, użyj aSupplier
, jeśli nie, użyjCallable
.źródło
Oba są normalnymi interfejsami Java bez specjalnej semantyki. Callable jest częścią współbieżnego API. Dostawca jest częścią nowego funkcjonalnego API programowania. Można je tworzyć z wyrażeń lambda dzięki zmianom w Javie8. @Funkcjonalny interfejs sprawia, że kompilator sprawdza, czy interfejs działa, i generuje błąd, jeśli tak nie jest, ale interfejs nie potrzebuje tej adnotacji, aby był interfejsem funkcjonalnym i musi zostać zaimplementowany przez lambdas. To tak, jak metoda może być zastąpieniem bez oznaczenia @Override, ale nie odwrotnie.
Możesz zdefiniować własne interfejsy kompatybilne z lambdami i dokumentować je za pomocą
@FunctionalInterface
adnotacji. Dokumentowanie jest jednak opcjonalne.źródło
IntPredicate
w Javie.