Type vide en Java

1. Vue d'ensemble

En tant que développeurs Java, nous avons peut-être rencontré le type Void à une occasion et nous nous sommes demandé quel était son but.

Dans ce tutoriel rapide, nous allons en apprendre davantage sur cette classe particulière et voir quand et comment l'utiliser ainsi que comment éviter de l'utiliser lorsque cela est possible.

2. Quel est le type de vide

Depuis JDK 1.1, Java nous fournit le type Void . Son but est simplement de représenter le type de retour void comme une classe et de contenir une valeur publique de classe . Il n'est pas instanciable car son seul constructeur est privé.

Par conséquent, la seule valeur que nous pouvons attribuer à une variable Void est null . Cela peut sembler un peu inutile, mais nous verrons maintenant quand et comment utiliser ce type.

3. Usages

Il existe certaines situations où l'utilisation du type Void peut être intéressante.

3.1. Réflexion

Premièrement, nous pourrions l'utiliser lors de la réflexion. En effet, le type de retour de toute méthode void correspondra à la variable Void.TYPE qui contient la valeur Class mentionnée précédemment .

Imaginons une classe de calculatrice 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); } }

Certaines méthodes renvoient un entier, d'autres ne renvoient rien. Maintenant, disons que nous devons récupérer, par réflexion, toutes les méthodes qui ne retournent aucun résultat . Nous y parviendrons en utilisant 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())); }

Comme nous pouvons le voir, seules les méthodes clear () et print () ont été récupérées.

3.2. Génériques

Une autre utilisation du type Void est avec les classes génériques. Supposons que nous appelons une méthode qui nécessite un paramètre Callable :

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

Mais l' appelable que nous voulons passer n'a rien à renvoyer. Par conséquent, nous pouvons passer 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(); }

Nous aurions pu utiliser un type aléatoire (par exemple Callable ) et retourner un type nul ou aucun type ( Callable) , mais l' utilisation de Void énonce clairement nos intentions.

Nous pouvons également appliquer cette méthode aux lambdas. En fait, notre Callable aurait pu être écrit comme un lambda. Imaginons une méthode nécessitant une fonction , mais nous voulons utiliser une fonction qui ne renvoie rien. Ensuite, il suffit de lui faire revenir 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. Comment éviter de l'utiliser?

Maintenant, nous avons vu quelques utilisations du type Void . Cependant, même si la première utilisation est tout à fait correcte, nous pourrions vouloir éviter d'utiliser Void dans les génériques si possible . En effet, rencontrer un type de retour qui représente l'absence de résultat et ne peut contenir que null peut être fastidieux.

Nous allons maintenant voir comment éviter ces situations. Tout d'abord, considérons notre méthode avec le paramètre Callable . Afin d'éviter d'utiliser un Callable , nous pourrions proposer une autre méthode prenant un paramètre Runnable à la place:

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

On peut donc lui passer un Runnable qui ne retourne aucune valeur et ainsi se débarrasser du inutile return null :

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

Mais alors, que se passe-t-il si la classe Defer n'est pas à nous de modifier? Ensuite, nous pouvons soit nous en tenir à l' option Callable , soit créer une autre classe prenant un Runnable et différant l'appel à la 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; } }); } }

En faisant cela, nous encapsulons une fois pour toutes la partie encombrante dans notre propre méthode, permettant aux futurs développeurs d'utiliser une API plus simple.

Bien sûr, la même chose peut être obtenue pour Function . Dans notre exemple, la fonction ne renvoie rien, nous pouvons donc fournir une autre méthode prenant un consommateur à la place:

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

Alors, que faire si notre fonction ne prend aucun paramètre? Nous pouvons soit utiliser un Runnable, soit créer notre propre interface fonctionnelle (si cela semble plus clair):

public interface Action { void execute(); }

Ensuite, nous surchargons à nouveau la méthode defer () :

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

5. Conclusion

Dans ce court article, nous avons couvert la classe Java Void . Nous avons vu quel était son but et comment l'utiliser. Nous avons également appris quelques alternatives à son utilisation.

Comme d'habitude, le code complet de cet article est disponible sur notre GitHub.