Вернуть CompletableFuture ‹Void› или CompletableFuture ‹?›?

Я хочу написать асинхронный метод, который возвращает CompletableFuture. Единственная цель в будущем - отслеживать, когда метод завершен, а не его результат. Лучше вернуть CompletableFuture<Void> или CompletableFuture<?>? Есть ли причина предпочесть одно или другое, или они взаимозаменяемы?

  • Сам CompletableFuture возвращает CompletableFuture<Void> из многих своих методов.
  • java.nio имеет Future<Void> в AsynchronousSocketChannel: Future<Void> connect(SocketAddress remote).
  • С другой стороны, java.util.concurrent классы, такие как ExecutorService и ScheduledExecutorService вернуть Future<?>: например, с Future<?> submit(Runnable task).

Обратите внимание, что я спрашиваю только о типах возвращаемых данных, а не о списках параметров, объявлениях переменных или других контекстах.


person John Kugelman    schedule 11.12.2015    source источник


Ответы (4)


Лучше всего использовать CompletableFuture<Void>.

Согласно этому ответу, найденному Сотириос Делиманолис, Future<?> - незначительный недостаток API. В Java 6 метод submit() использовал внутри Future<Object>, поэтому его возвращаемый тип был установлен на Future<?>. В Java 7 реализация изменилась на внутреннее использование Future<Void>, но было слишком поздно менять API, поэтому возвращаемое значение осталось как Future<?>.

Новые API Java используют Future<Void> и CompletableFuture<Void>. Это примеры, которым мы должны следовать.

person John Kugelman    schedule 17.08.2016

Было бы лучше вернуть CompletableFuture ‹Void› или CompletableFuture ‹?›

Есть ли причина предпочесть одно или другое, или они взаимозаменяемы?

Код может повлиять на три контекста:

  • Время выполнения - дженерики не имеют никакого отношения к этому.
  • Компиляция - я не могу представить себе случай, когда какой-то метод будет принимать Future<Void>, но не принимает Future<?>.
  • Разработка - если результат Future не имеет значения, то рекомендуется сообщить об этом пользователям через объявление.

Так что Future<Void> предпочтительнее.

person user3707125    schedule 11.12.2015

Посмотрев на CompletableFuture API, вы обнаружите, что CompletableFuture<Void> используется с такими методами, как побочные эффекты, где результат не может быть получен (потому что он не существует), например:

CompletableFuture.runAsync(Runnable runnable);

возврат CompletableFuture<Object> здесь может сбить с толку, потому что на самом деле результата нет, мы заботимся только о завершении. Методы, которые принимают Consumers и Runnables, возвращают CompletableFuture<Void>, например: thenAccept, thenAcceptAsync. Consumer и Runnable используются для устранения побочных эффектов в целом.

Другой вариант использования Void - когда вы действительно не знаете результата. Например: CompletableFuture.allOf, переданный список может быть CompletableFuture, полученным из Runnable, поэтому мы не можем получить результат.

Сказав все это, CompletableFuture<Void> хорош только в том случае, если у вас нет другого варианта, если вы можете вернуть результат, тогда, пожалуйста, сделайте это, вызывающий может отказаться от него, если он не заинтересован. Вы сказали, что вас интересует только завершение, тогда да, CompletableFuture<Void> выполнит эту работу, но ваши пользователи API возненавидят вас, если узнают, что CompletableFuture<T> был вариантом, и вы просто решили от их имени, что им никогда не понадобится результат.

person Sleiman Jneidi    schedule 11.12.2015

Подходящий тип зависит от его семантики. Все перечисленные параметры обещают сигнализировать о завершении и могут асинхронно возвращать исключения.

  • CompletableFuture<Void>: Void сообщает пользователю, что результата не ожидается.
  • CompletableFuture<?> ? означает, что тип содержащегося значения не определен в том смысле, что может быть доставлено любое значение.

Класс CompletableFuture наследует несколько вспомогательных методов от CompletionStage. Но он также позволяет вызывающей стороне вашего метода инициировать завершение будущего, что кажется неправильным, потому что ваш метод сам отвечает за сигнализирование о его завершении. Существует также метод cancel(...), который в стандартной реализации CompletableFuture не имеет смысла, поскольку не отменяет выполнение.

  • Future<Void>: Void сообщает пользователю, что результата ожидать не стоит.
  • Future<?> ? означает, что тип содержащегося значения не определен в том смысле, что может быть доставлено любое значение.

Future не хватает удобных методов из CompletionStage. Это не позволяет инициировать завершение будущего, но выполнение может быть отменено.

Следующий вариант - CompletionStage<Void>:

  • CompletionStage<Void>: Void сообщает пользователю, что ожидаемого результата нет. Присутствуют удобные методы привязки обработчиков, но нет cancel(...) метода. Вызывающий ваш метод не может инициировать завершение CompletionStage.
  • <CancellableFuture extends Future<Void> & CompletionStage<Void>>: Набор методов из Future<Void> и CompletionStage<Void>. Сообщает, что результата нет, есть удобные методы и возможность отмены. Вызывающий ваш метод не может инициировать завершение CompletionStage.

Отсутствие метода cancel(...) может соответствовать вашему сценарию или нет. Поэтому я предлагаю использовать CompletionStage<Void>, если вам не нужна отмена, и <CancellableFuture extends Future<Void> & CompletionStage<Void>>, если вам требуется возможность отмены выполнения. Если вы выбрали <CancellableFuture extends Future<Void> & CompletionStage<Void>>, вы, вероятно, захотите создать интерфейс, наследующий от Future<Void> и CompletionStage<Void>, который будет использоваться в качестве возвращаемого типа, вместо того, чтобы помещать пересечение длинных типов непосредственно в объявление вашего метода.

Вам следует избегать возврата с объявленным возвращаемым типом CompletableFuture из-за возможности для вызывающей стороны инициировать завершение в будущем. Это умышленно приводит к путанице в коде и неожиданным зависаниям, потому что неясно, какой код больше отвечает за запуск завершения. Используйте один из упомянутых более ограниченных типов, чтобы система типов предотвращала непреднамеренное срабатывание завершения вызывающей стороной вашего метода.

person Augustus Kling    schedule 26.11.2016