1. Información general
Como desarrolladores de Java, podríamos habernos encontrado con el tipo Void en alguna ocasión y preguntarnos cuál era su propósito.
En este tutorial rápido, aprenderemos sobre esta clase peculiar y veremos cuándo y cómo usarla, así como cómo evitar usarla cuando sea posible.
2. ¿Cuál es el tipo de vacío?
Desde JDK 1.1, Java nos proporciona el tipo Void . Su propósito es simplemente representar el tipo de retorno vacío como una clase y contener un valor público de clase . No es instanciable ya que su único constructor es privado.
Por lo tanto, el único valor que podemos asignar a una variable Void es nulo . Puede parecer un poco inútil, pero ahora veremos cuándo y cómo usar este tipo.
3. Usos
Hay algunas situaciones en las que usar el tipo Void puede resultar interesante.
3.1. Reflexión
Primero, podríamos usarlo al hacer una reflexión. De hecho, el tipo de retorno de cualquier método void coincidirá con la variable Void.TYPE que contiene el valor Class mencionado anteriormente .
Imaginemos una clase de Calculadora simple :
public class Calculator { private int result = 0; public int add(int number) { return result += number; } public int sub(int number) { return result -= number; } public void clear() { result = 0; } public void print() { System.out.println(result); } }
Algunos métodos devuelven un número entero, otros no devuelven nada. Ahora, digamos que tenemos que recuperar, por reflexión, todos los métodos que no devuelven ningún resultado . Lo lograremos usando la variable Void.TYPE :
@Test void givenCalculator_whenGettingVoidMethodsByReflection_thenOnlyClearAndPrint() { Method[] calculatorMethods = Calculator.class.getDeclaredMethods(); List calculatorVoidMethods = Arrays.stream(calculatorMethods) .filter(method -> method.getReturnType().equals(Void.TYPE)) .collect(Collectors.toList()); assertThat(calculatorVoidMethods) .allMatch(method -> Arrays.asList("clear", "print").contains(method.getName())); }
Como podemos ver, solo se han recuperado los métodos clear () e print () .
3.2. Genéricos
Otro uso del tipo Void es con clases genéricas. Supongamos que estamos llamando a un método que requiere un parámetro invocable :
public class Defer { public static V defer(Callable callable) throws Exception { return callable.call(); } }
Pero, el Callable que queremos pasar no tiene que devolver nada. Por lo tanto, podemos pasar un Callable :
@Test void givenVoidCallable_whenDiffer_thenReturnNull() throws Exception { Callable callable = new Callable() { @Override public Void call() { System.out.println("Hello!"); return null; } }; assertThat(Defer.defer(callable)).isNull(); }
Podríamos usar un tipo aleatorio (por ejemplo, invocable ) y devolver nulo o ningún tipo ( invocable) , pero el uso de Void indica claramente nuestras intenciones.
También podemos aplicar este método a lambdas. De hecho, nuestro Callable podría haberse escrito como lambda. Imaginemos un método que requiera una función , pero queremos usar una función que no devuelva nada. Entonces solo tenemos que hacer que vuelva Void :
public static R defer(Function function, T arg) { return function.apply(arg); }
@Test void givenVoidFunction_whenDiffer_thenReturnNull() { Function function = s -> { System.out.println("Hello " + s + "!"); return null; }; assertThat(Defer.defer(function, "World")).isNull(); }
4. ¿Cómo evitar su uso?
Ahora, hemos visto algunos usos del tipo Vacío . Sin embargo, incluso si el primer uso está totalmente bien, es posible que deseemos evitar el uso de Void en genéricos si es posible . De hecho, encontrar un tipo de retorno que represente la ausencia de un resultado y solo pueda contener un valor nulo puede ser engorroso.
Ahora veremos cómo evitar estas situaciones. Primero, consideremos nuestro método con el parámetro Callable . Para evitar usar un Callable , podríamos ofrecer otro método que tome un parámetro Runnable en su lugar:
public static void defer(Runnable runnable) { runnable.run(); }
Entonces, podemos pasarle un Runnable que no devuelve ningún valor y así deshacernos del retorno nulo inútil :
Runnable runnable = new Runnable() { @Override public void run() { System.out.println("Hello!"); } }; Defer.defer(runnable);
Pero entonces, ¿qué pasa si la clase Defer no es nuestra para modificar? Luego, podemos ceñirnos a la opción Callable o crear otra clase tomando un Runnable y diferiendo la llamada a la clase Defer :
public class MyOwnDefer { public static void defer(Runnable runnable) throws Exception { Defer.defer(new Callable() { @Override public Void call() { runnable.run(); return null; } }); } }
Al hacer eso, encapsulamos la parte engorrosa de una vez por todas en nuestro propio método, permitiendo a los futuros desarrolladores usar una API más simple.
Por supuesto, se puede lograr lo mismo para Function . En nuestro ejemplo, la función no devuelve nada, por lo que podemos proporcionar otro método tomando un consumidor en su lugar:
public static void defer(Consumer consumer, T arg) { consumer.accept(arg); }
Entonces, ¿qué pasa si nuestra función no toma ningún parámetro? Podemos usar un Runnable o crear nuestra propia interfaz funcional (si eso parece más claro):
public interface Action { void execute(); }
Luego, sobrecargamos el método defer () nuevamente:
public static void defer(Action action) { action.execute(); }
Action action = () -> System.out.println("Hello!"); Defer.defer(action);
5. Conclusión
En este breve artículo, cubrimos la clase Java Void . Vimos cuál era su propósito y cómo usarlo. También aprendimos algunas alternativas a su uso.
Como de costumbre, el código completo de este artículo se puede encontrar en nuestro GitHub.