Я перешел на Java с C # после некоторых рекомендаций от CodeReview. Поэтому, когда я изучал LWJGL, я вспомнил одну вещь: каждый вызов Display
должен выполняться в том же потоке, в котором Display.create()
был вызван метод. Помня об этом, я выбрал класс, который выглядит примерно так.
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());
}
}
При написании этого класса вы заметите, что я создал метод с именем, isClosed()
который возвращает Future<Boolean>
. Это отправляет функции на мой Scheduler
интерфейс (который является не более чем оболочка вокруг ScheduledExecutorService
. При написании schedule
метода на Scheduler
я заметил , что я мог бы либо использовать Supplier<T>
аргумент или в Callable<T>
аргумент для представления функции, которая передается в. ScheduledExecutorService
Не содержат переопределить, Supplier<T>
но я заметил, что лямбда-выражение () -> Display.isCloseRequested()
на самом деле совместимо с типами Callable<bool>
и Supplier<bool>
.
Мой вопрос заключается в том, есть ли разница между этими двумя, семантически или иным образом, и если да, то каково это, чтобы я мог придерживаться этого?
Ответы:
Короткий ответ заключается в том, что оба используют функциональные интерфейсы, но также стоит отметить, что не все функциональные интерфейсы должны иметь
@FunctionalInterface
аннотацию. Критическая часть JavaDoc гласит:И самое простое определение функционального интерфейса (просто, без других исключений) просто:
Следовательно, в ответе @Maciej Chalapuk также можно отбросить аннотацию и указать желаемую лямбду:
Теперь, что делает оба
Callable
иSupplier
функциональные интерфейсы, потому что они содержат ровно один абстрактный метод:Callable.call()
Supplier.get()
Поскольку оба метода не принимают аргумента (в отличие от
MyInterface.myCall(int)
примера), формальные параметры пусты (()
).Как вы уже поняли, это возможно потому, что оба абстрактных метода будут возвращать тип выражения, которое вы используете. Вы должны определенно использовать
Callable
данное использованиеScheduledExecutorService
.Дальнейшее исследование (за рамками вопроса)
Оба интерфейса происходят из совершенно разных пакетов , поэтому они также используются по-разному. В вашем случае я не вижу, как
Supplier<T>
будет использоваться реализация, если она не предоставляетCallable
:Первое
() ->
можно свободно интерпретировать как «Supplier
дает ...», а второе как «Callable
дает ...».return value;
это телоCallable
лямбды, которое само является теломSupplier
лямбды.Однако, использование в этом надуманный пример получает немного сложно, так как теперь нужно
get()
изSupplier
первых , прежде чемget()
-ting ваш результат отFuture
, который будет , в свою очередьcall()
СВОЙCallable
асинхронно.источник
Одно из основных различий между двумя интерфейсами состоит в том, что Callable позволяет создавать проверенные исключения изнутри реализации, в то время как Поставщик этого не делает.
Вот фрагменты кода из JDK, подчеркивающие это -
источник
Supplier
, таких как потоковый API. Вы абсолютно можете передавать лямбды и ссылки на методы к методам, которые принимаютCallable
.Как вы заметили, на практике они делают одно и то же (предоставляют какую-то ценность), однако в принципе они предназначены для разных вещей:
A
Callable
- это « Задача, которая возвращает результат , а aSupplier
-« поставщик результатов ». Другими словами, aCallable
- это способ ссылки на еще не выполненную единицу работы, в то время как aSupplier
- это способ ссылки на еще неизвестное значение.Возможно, что он
Callable
мог сделать очень мало работы и просто вернуть значение. Также возможно, чтоSupplier
можно сделать довольно много работы (например, построить большую структуру данных). Но в целом то, что вас волнует, является их главной целью. Например,ExecutorService
работает сCallable
s, потому что его основная цель - выполнить единицы работы. Хранилище данных с отложенной загрузкой будет использовать aSupplier
, потому что оно заботится о предоставлении значения, без особого беспокойства о том, сколько работы может потребоваться.Другой способ сформулировать различие состоит в том, что a
Callable
может иметь побочные эффекты (например, запись в файл), тогда как aSupplier
обычно не должно иметь побочных эффектов. В документации явно не упоминается об этом (поскольку это не является обязательным требованием ), но я бы посоветовал подумать об этом. Если работа идемпотентна, используйте aSupplier
, если не используйте aCallable
.источник
Оба являются обычными интерфейсами Java без специальной семантики. Callable является частью параллельного API. Поставщик является частью нового API функционального программирования. Они могут быть созданы из лямбда-выражений благодаря изменениям в Java8. @FunctionalInterface заставляет компилятор проверять работоспособность интерфейса и выдает ошибку, если это не так, но интерфейсу не требуется, чтобы эта аннотация была функциональным интерфейсом и была реализована с помощью лямбды. Это похоже на то, как метод может быть переопределен без пометки @Override, но не наоборот.
Вы можете определить свои собственные интерфейсы, совместимые с лямбдами, и задокументировать их с
@FunctionalInterface
аннотацией. Документирование необязательно, хотя.источник
IntPredicate
в Java.