Tipo di vuoto in Java

1. Panoramica

Come sviluppatori Java, potremmo aver incontrato il tipo Void in qualche occasione e ci siamo chiesti quale fosse il suo scopo.

In questo breve tutorial, impareremo a conoscere questa particolare classe e vedremo quando e come usarla e come evitare di usarla quando possibile.

2. Qual è il tipo di vuoto

A partire da JDK 1.1, Java ci fornisce il tipo Void . Il suo scopo è semplicemente quello di rappresentare il tipo restituito void come una classe e contenere un valore pubblico Class . Non è istanziabile poiché il suo unico costruttore è privato.

Pertanto, l'unico valore che possiamo assegnare a una variabile Void è nullo . Può sembrare un po 'inutile, ma ora vedremo quando e come utilizzare questo tipo.

3. Usi

Ci sono alcune situazioni in cui l'utilizzo del tipo Void può essere interessante.

3.1. Riflessione

Innanzitutto, potremmo usarlo quando facciamo riflessione. In effetti, il tipo restituito di qualsiasi metodo void corrisponderà alla variabile Void.TYPE che contiene il valore Class menzionato in precedenza .

Immaginiamo una semplice classe Calcolatrice :

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); } }

Alcuni metodi restituiscono un numero intero, altri non restituiscono nulla. Ora, diciamo che dobbiamo recuperare, per riflessione, tutti i metodi che non restituiscono alcun risultato . Otterremo questo risultato utilizzando la variabile 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())); }

Come possiamo vedere, sono stati recuperati solo i metodi clear () e print () .

3.2. Generici

Un altro utilizzo del tipo Void è con classi generiche. Supponiamo di chiamare un metodo che richiede un parametro Callable :

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

Ma il Callable che vogliamo passare non deve restituire nulla. Pertanto, possiamo passare 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(); }

Potremmo usare un tipo casuale (es. Callable ) e restituire null o nessun tipo ( Callable) , ma l' uso di Void afferma chiaramente le nostre intenzioni.

Possiamo anche applicare questo metodo a lambda. È un dato di fatto, il nostro Callable avrebbe potuto essere scritto come lambda. Immaginiamo un metodo che richiede una funzione , ma vogliamo utilizzare una funzione che non restituisce nulla. Quindi non ci resta che farlo tornare 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. Come evitare di usarlo?

Ora, abbiamo visto alcuni utilizzi del tipo Void . Tuttavia, anche se il primo utilizzo va bene, potremmo voler evitare di utilizzare Void nei generici, se possibile . Infatti, incontrare un tipo restituito che rappresenta l'assenza di un risultato e può contenere solo null può essere complicato.

Vedremo ora come evitare queste situazioni. Per prima cosa, consideriamo il nostro metodo con il parametro Callable . Per evitare di utilizzare un Callable , potremmo offrire un altro metodo che prende invece un parametro Runnable :

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

Quindi, possiamo passargli un Runnable che non restituisce alcun valore e quindi eliminare l'inutile return null :

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

Ma allora, cosa succede se la classe Defer non è nostra da modificare? Quindi possiamo attenerci all'opzione Callable o creare un'altra classe prendendo un Runnable e rinviando la chiamata alla classe Defer :

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

In questo modo, incapsuliamo la parte ingombrante una volta per tutte nel nostro metodo, consentendo ai futuri sviluppatori di utilizzare un'API più semplice.

Ovviamente, lo stesso può essere ottenuto per Function . Nel nostro esempio, la funzione non restituisce nulla, quindi possiamo fornire un altro metodo che prende invece un consumatore :

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

Allora, cosa succede se la nostra funzione non accetta alcun parametro? Possiamo utilizzare un Runnable o creare la nostra interfaccia funzionale (se sembra più chiaro):

public interface Action { void execute(); }

Quindi, sovraccarichiamo di nuovo il metodo defer () :

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

5. conclusione

In questo breve articolo, abbiamo trattato la classe Java Void . Abbiamo visto qual era il suo scopo e come usarlo. Abbiamo anche appreso alcune alternative al suo utilizzo.

Come al solito, il codice completo di questo articolo può essere trovato sul nostro GitHub.