Tipo de Vazio em Java
1. Visão geral
Como desenvolvedores Java, podemos ter encontrado o tipoVoid em alguma ocasião e nos perguntado qual era sua finalidade.
Neste tutorial rápido, vamos aprender sobre essa classe peculiar e ver quando e como usá-la, bem como evitar usá-la quando possível.
2. Qual é o tipo deVoid
Desde o JDK 1.1, Java nos fornece o tipoVoid. Its purpose is simply to represent the void return type as a class and contain a Class<Void> public value. Não é instanciado porque seu único construtor é privado.
Portanto, o único valor que podemos atribuir a uma variávelVoid énull. Pode parecer um pouco inútil, mas agora veremos quando e como usar esse tipo.
3. Usos
Existem algumas situações em que usar o tipoVoid pode ser interessante.
3.1. Reflexão
Primeiro, poderíamos usá-lo ao refletir. Na verdade,the return type of any void method will match the Void.TYPE variable that holds the Class<Void> value mentioned earlier.
Vamos imaginar uma classeCalculator simples:
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);
}
}
Alguns métodos estão retornando um número inteiro, outros não estão retornando nada. Agora, digamos que temos deretrieve, by reflection, all methods that don’t return any result. Faremos isso usando a variávelVoid.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, apenas os métodosclear()eprint() foram recuperados.
3.2. Genéricos
Outro uso do tipoVoid é com classes genéricas. Suponhamos que estejamos chamando um método que requer um parâmetroCallable:
public class Defer {
public static V defer(Callable callable) throws Exception {
return callable.call();
}
}
Mas, oCallable que queremos passar não precisa retornar nada. Portanto, podemos passar umCallable<Void>:
@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();
}
Poderíamos usar um tipo aleatório (por exemplo, Callable<Integer>) e retornanull ou nenhum tipo (Callable), masusing Void states our intentions clearly.
Também podemos aplicar esse método às lambdas. Na verdade, nossoCallable poderia ter sido escrito como lambda. Vamos imaginar um método que requer umFunction, mas queremos usar umFunction que não retorna nada. Então, só precisamos fazer com que ele retorneVoid:
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. Como evitar usá-lo?
Agora, vimos alguns usos do tipoVoid. No entanto, mesmo que o primeiro uso seja totalmente adequado,we might want to avoid using Void in generics if possible. Na verdade, encontrar um tipo de retorno que representa a ausência de um resultado e pode conter apenasnull pode ser complicado.
Veremos agora como evitar essas situações. Primeiro, vamos considerar nosso método com o parâmetroCallable. In order to avoid using a Callable<Void>, we might offer another method taking a Runnable parameter instead:
public static void defer(Runnable runnable) {
runnable.run();
}
Então, podemos passar umRunnable que não retorna nenhum valor e, assim, nos livrar dosreturn null inúteis:
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Hello!");
}
};
Defer.defer(runnable);
Mas então, e se a classeDefer não for nossa para modificar? Então, podemos ficar com a opçãoCallable<Void> oucreate another class taking a Runnable and deferring the call to the Defer class:
public class MyOwnDefer {
public static void defer(Runnable runnable) throws Exception {
Defer.defer(new Callable() {
@Override
public Void call() {
runnable.run();
return null;
}
});
}
}
Ao fazer isso, encapsulamos a parte complicada de uma vez por todas em nosso próprio método, permitindo que desenvolvedores futuros usem uma API mais simples.
Obviamente, o mesmo pode ser alcançado paraFunction. Em nosso exemplo,Function não retorna nada, portanto, podemos fornecer outro método usando umConsumer em vez disso:
public static void defer(Consumer consumer, T arg) {
consumer.accept(arg);
}
Então, e se nossa função não tiver nenhum parâmetro? Podemos usar umRunnable ou criar nossa própria interface funcional (se isso parecer mais claro):
public interface Action {
void execute();
}
Em seguida, sobrecarregamos o métododefer() novamente:
public static void defer(Action action) {
action.execute();
}
Action action = () -> System.out.println("Hello!");
Defer.defer(action);
5. Conclusão
Neste breve artigo, cobrimos a classe JavaVoid. Vimos qual era o seu propósito e como usá-lo. Também aprendemos algumas alternativas para seu uso.
Como de costume, o código completo deste artigo pode ser encontrado emour GitHub.