java - completionstage - ¿Devuelve CompletableFuture<Void> o CompletableFuture<?>?
java promise (4)
¿Sería mejor devolver CompletableFuture <Void> o CompletableFuture <?>
¿Hay alguna razón para preferir uno u otro, o son intercambiables?
Hay tres contextos que el código puede afectar:
- Tiempo de ejecución: los genéricos no tienen ninguna implicación.
- Compilar: no puedo imaginar un caso en el que algún método acepte
Future<Void>
pero no acepteFuture<?>
. - Desarrollo: si el resultado de
Future
no tiene ningún significado, es una buena práctica decirlo a los usuarios a través de la declaración.
Entonces Future<Void>
es más preferible.
Quiero escribir un método asíncrono que devuelva un CompletableFuture
. El único propósito del futuro es rastrear cuándo se completa el método, no su resultado. ¿Sería mejor devolver CompletableFuture<Void>
o CompletableFuture<?>
? ¿Hay alguna razón para preferir uno u otro, o son intercambiables?
-
CompletableFuture
devuelveCompletableFuture<Void>
de muchos de sus métodos. -
java.nio
tiene unFuture<Void>
enAsynchronousSocketChannel
:Future<Void> connect(SocketAddress remote)
. - Por otro lado, las clases
java.util.concurrent
comoExecutorService
yScheduledExecutorService
devuelvenFuture<?>
: Por ejemplo, conFuture<?> submit(Runnable task)
.
Tenga en cuenta que solo pregunto sobre los tipos de retorno, no las listas de parámetros, las declaraciones de variables u otros contextos.
Al CompletableFuture
API CompletableFuture
, encontrará que CompletableFuture<Void>
se utiliza con los métodos de efectos secundarios en los que no se puede obtener el resultado (porque no existe), por ejemplo:
CompletableFuture.runAsync(Runnable runnable);
devolver un CompletableFuture<Object>
aquí sería confuso porque realmente no hay resultados, solo nos importa la finalización. Métodos que llevan a Consumers
y Runnables
devolver CompletableFuture<Void>
, por ejemplo: thenAccept
, thenAcceptAsync
. Consumer
y Runnable
se usan para efectos secundarios en general.
Otro caso de uso para Void
es cuando realmente no conoces el resultado. Por ejemplo: CompletableFuture.allOf
, la lista aprobada puede ser un CompletableFuture originado a partir de Runnable, por lo que no podemos obtener el resultado.
Habiendo dicho todo eso, CompletableFuture<Void>
solo es bueno si no tiene otra opción, si puede devolver el resultado, por favor vaya, la persona que llama puede optar por descartarlo si no está interesado. Dijiste que solo te interesaba completarlo, entonces sí, CompletableFuture<Void>
haría el trabajo, pero los usuarios de API te odiarían si supieran que CompletableFuture<T>
era una opción y tú simplemente decidiste en su nombre que Nunca necesitarás el resultado.
El tipo adecuado depende de su semántica. Todas las opciones enumeradas prometen señalar la finalización y pueden devolver excepciones de forma asíncrona.
-
CompletableFuture<Void>
: elVoid
le dice al usuario que no hay resultados esperados. -
CompletableFuture<?>
The?
significa que el tipo del valor que contiene no está definido en el sentido de que se puede entregar cualquier valor.
La clase CompletableFuture
hereda varios métodos de conveniencia de CompletionStage
. Pero también permite que la persona que llama a su método dispare la finalización del futuro, lo que parece incorrecto porque su método es responsable de señalar su finalización. También hay un método de cancel(...)
que es bastante inútil en la implementación predeterminada de CompletableFuture
ya que no cancela la ejecución.
-
Future<Void>
: elVoid
le dice al usuario que no se espera ningún resultado. -
Future<?>
El?
significa que el tipo del valor que contiene no está definido en el sentido de que se puede entregar cualquier valor.
Future
carece de los métodos de conveniencia de CompletionStage
. No permite desencadenar la finalización del futuro, pero la ejecución puede ser cancelada.
La siguiente opción es CompletionStage<Void>
:
-
CompletionStage<Void>
: elVoid
le dice al usuario que no hay resultados esperados. Los métodos de conveniencia para enlazar controladores están presentes, pero el método decancel(...)
no lo está. La persona que llama de su método no puede activar la finalización de unCompletionStage
. -
<CancellableFuture extends Future<Void> & CompletionStage<Void>>
: conjunto de métodos deFuture<Void>
yCompletionStage<Void>
. Indica que no hay resultados, que existen métodos de conveniencia y la opción de cancelar. La persona que llama de su método no puede activar la finalización de unCompletionStage
.
La ausencia del método de cancel(...)
puede adaptarse a su escenario o no. Por lo tanto, sugiero ir con CompletionStage<Void>
si no necesita cancelación y use <CancellableFuture extends Future<Void> & CompletionStage<Void>>
si necesita la opción de cancelar la ejecución. Si eliges <CancellableFuture extends Future<Void> & CompletionStage<Void>>
, probablemente quieras crear una interfaz que herede de Future<Void>
y CompletionStage<Void>
para usar como return type en lugar de poner la intersección de tipo largo directamente en su declaración de método.
Debe evitar regresar con un tipo de retorno declarado CompletableFuture
debido a la posibilidad de que la persona que llama dispare la finalización del futuro. Si lo hace de forma deliberada, se genera un código confuso y se cuelga de manera sorprendente, ya que no está claro qué código es el responsable de desencadenar la finalización. Utilice uno de los tipos más restringidos mencionados para permitir que el sistema de tipos evite la activación involuntaria por parte del llamador de su método.
Es mejor usar CompletableFuture<Void>
.
Según esta respuesta encontrada por Sotirios Delimanolis , Future<?>
Es un defecto menor de API. En Java 6, el método submit()
usaba un Future<Object>
, por lo que su tipo de retorno se configuró como Future<?>
. En Java 7, la implementación cambió para usar Future<Void>
internamente, pero ya era demasiado tarde para cambiar la API, por lo que el valor de retorno se mantuvo como Future<?>
.
Las API de Java más nuevas utilizan Future<Void>
y CompletableFuture<Void>
. Esos son los ejemplos que debemos seguir.