Tipo Void em Java

Overview

Como desenvolvedores Java, podemos ter encontrado o tipo Void em alguma ocasião e nos perguntamos qual era o seu propósito.

Neste tutorial rápido, vamos aprender sobre esta classe peculiar e ver quando e como usá-la, bem como como evitar usá-la quando possível.

Qual é o tipo de Void

Desde JDK 1.1, Java nos fornece o tipo Void. Seu propósito é simplesmente representar o tipo de retorno de vazio como uma classe e conter uma Classe<Void>valor público. Não é instanciável, pois seu único construtor é privado.

Por isso, o único valor que podemos atribuir a uma variável Void é nulo. Pode parecer um pouco inútil, mas agora vamos ver quando e como usar este tipo.

Usages

Existem algumas situações em que usar o tipo Void pode ser interessante.

3.1. Reflexão

Primeiro, podemos usá-lo ao fazer a reflexão. Na verdade, o tipo de retorno de qualquer método vazio irá corresponder à variável Void.TYPE que detém a Classe<Void> valor mencionado anteriormente.

Vamos imaginar uma simples classe Calculadora:

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, alguns não estão retornando nada. Agora, digamos que temos de recuperar, por reflexão, todos os métodos que não retornam nenhum resultado. Vamos conseguir isto usando a variável Void.TYPE:

@Testvoid givenCalculator_whenGettingVoidMethodsByReflection_thenOnlyClearAndPrint() { Method calculatorMethods = Calculator.class.getDeclaredMethods(); List<Method> 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étodos clear() e print() foram recuperados.

3.2. Generics

Um outro uso do tipo Void é com classes genéricas. Vamos supor que estamos chamando um método que requer um parâmetro Chamável:

public class Defer { public static <V> V defer(Callable<V> callable) throws Exception { return callable.call(); }}

Mas, o Chamável que queremos passar não tem que retornar nada. Portanto, podemos passar um Callable<Void>:

@Testvoid givenVoidCallable_whenDiffer_thenReturnNull() throws Exception { Callable<Void> callable = new Callable<Void>() { @Override public Void call() { System.out.println("Hello!"); return null; } }; assertThat(Defer.defer(callable)).isNull();}

Podemos usar um tipo aleatório (por exemplo, Callable<Integer>) e retornar nulo ou nenhum tipo (Callable), mas usando o Void diz claramente nossas intenções.

Também podemos aplicar este método aos lambdas. De facto, o nosso Callable poderia ter sido escrito como um lambda. Vamos imaginar um método que requer uma Função, mas queremos usar uma Função que não retorne nada. Então só temos que fazer retornar Void:

public static <T, R> R defer(Function<T, R> function, T arg) { return function.apply(arg);}
@Testvoid givenVoidFunction_whenDiffer_thenReturnNull() { Function<String, Void> function = s -> { System.out.println("Hello " + s + "!"); return null; }; assertThat(Defer.defer(function, "World")).isNull();}

Como evitar usá-la?

Agora, já vimos alguns usos do tipo Void. Entretanto, mesmo que o primeiro uso seja totalmente bom, podemos querer evitar o uso do Void em genéricos, se possível. De facto, encontrar um tipo de retorno que representa a ausência de um resultado e só pode conter nulo pode ser incómodo.

Vemos agora como evitar estas situações. Primeiro, vamos considerar o nosso método com o parâmetro Callable. Para evitar usar um Callable<Void>, podemos oferecer outro método tomando um parâmetro Runnable:

public static void defer(Runnable runnable) { runnable.run();}

Então, podemos passar-lhe um Runnable que não retorna nenhum valor e assim nos livrarmos do inútil retorno null:

Runnable runnable = new Runnable() { @Override public void run() { System.out.println("Hello!"); }};Defer.defer(runnable);

Mas então, e se a classe Deferir não for nossa para modificar? Então podemos ou ficar com a opção Callable<Void> ou criar outra classe pegando uma Runnable e adiando a chamada para a classe Defer:

public class MyOwnDefer { public static void defer(Runnable runnable) throws Exception { Defer.defer(new Callable<Void>() { @Override public Void call() { runnable.run(); return null; } }); }}

Ao fazer isso, encapsulamos a parte incômoda de uma vez por todas em nosso próprio método, permitindo que futuros desenvolvedores usem uma API mais simples.

Obtendo, o mesmo pode ser alcançado para Function. Em nosso exemplo, a Função não retorna nada, assim podemos fornecer outro método tomando um Consumidor:

public static <T> void defer(Consumer<T> consumer, T arg) { consumer.accept(arg);}

Então, e se a nossa função não tomar nenhum parâmetro? Podemos usar uma Runnable ou criar nossa própria interface funcional (se isso parecer mais claro):

public interface Action { void execute();}

Então, nós sobrecarregamos o método defer() novamente:

public static void defer(Action action) { action.execute();}
Action action = () -> System.out.println("Hello!");Defer.defer(action);

Conclusion

Neste pequeno artigo, nós cobrimos a classe Java Void. Vimos qual era o seu propósito e como utilizá-la. Também aprendemos algumas alternativas ao seu uso.

Como de costume, o código completo deste artigo pode ser encontrado em nosso GitHub.

>

Comece com Spring 5 e Spring Boot 2, através do curso Learn Spring:

>> VERIFIQUE O CURSO

>

Deixe uma resposta

O seu endereço de email não será publicado.