web-dev-qa-db-fra.com

Comment spécifier les types de fonction pour les méthodes void (not Void) en Java8?

Je m'amuse avec Java 8 pour savoir comment fonctionne un citoyen de première classe. J'ai l'extrait suivant:

package test;

import Java.util.*;
import Java.util.function.*;

public class Test {

    public static void myForEach(List<Integer> list, Function<Integer, Void> myFunction) {
      list.forEach(functionToBlock(myFunction));
    }

    public static void displayInt(Integer i) {
      System.out.println(i);
    }


    public static void main(String[] args) {
      List<Integer> theList = new ArrayList<>();
      theList.add(1);
      theList.add(2);
      theList.add(3);
      theList.add(4);
      theList.add(5);
      theList.add(6);
      myForEach(theList, Test::displayInt);
    }
}

Ce que j'essaie de faire est de passer la méthode displayInt à la méthode myForEach en utilisant une référence de méthode. Pour compiler produit l'erreur suivante:

src/test/Test.Java:9: error: cannot find symbol
      list.forEach(functionToBlock(myFunction));
                   ^
  symbol:   method functionToBlock(Function<Integer,Void>)
  location: class Test
src/test/Test.Java:25: error: method myForEach in class Test cannot be applied to given ty
pes;
      myForEach(theList, Test::displayInt);
      ^
  required: List<Integer>,Function<Integer,Void>
  found: List<Integer>,Test::displayInt
  reason: argument mismatch; bad return type in method reference
      void cannot be converted to Void

Le compilateur se plaint que void cannot be converted to Void. Je ne sais pas comment spécifier le type de l'interface de fonction dans la signature de myForEach telle que le code soit compilé. Je sais que je pourrais simplement changer le type de retour de displayInt en Void, puis renvoyer null. Cependant, il peut y avoir des situations où il est impossible de modifier la méthode que je souhaite utiliser ailleurs. Existe-t-il un moyen simple de réutiliser displayInt tel quel?

114
Valentin

Vous essayez d'utiliser le mauvais type d'interface. Le type Fonction n'est pas approprié dans ce cas car il reçoit un paramètre et a une valeur de retour. Au lieu de cela, vous devriez utiliser Consumer (anciennement connu sous le nom de Block)

Le type de fonction est déclaré comme

interface Function<T,R> {
  R apply(T t);
}

Cependant, le type Consommateur est compatible avec ce que vous recherchez:

interface Consumer<T> {
   void accept(T t);
}

En tant que tel, Consumer est compatible avec les méthodes qui reçoivent un T et ne renvoient rien (vide). Et c'est ce que tu veux.

Par exemple, si je voulais afficher tous les éléments d'une liste, je pouvais simplement créer un consommateur pour cela avec une expression lambda:

List<String> allJedi = asList("Luke","Obiwan","Quigon");
allJedi.forEach( jedi -> System.out.println(jedi) );

Vous pouvez voir ci-dessus que, dans ce cas, l'expression lambda reçoit un paramètre et n'a aucune valeur de retour.

Maintenant, si je voulais utiliser une référence de méthode au lieu d'une expression lambda pour créer une consommation de ce type, alors j'ai besoin d'une méthode qui reçoit une chaîne et retourne void, non?.

Je pourrais utiliser différents types de références de méthode, mais dans ce cas, profitons d'une référence à une méthode d'objet en utilisant la méthode println dans la méthode System.out objet, comme ceci:

Consumer<String> block = System.out::println

Ou je pourrais simplement faire

allJedi.forEach(System.out::println);

La méthode println est appropriée car elle reçoit une valeur et a un type de retour void, tout comme la méthode accept de Consumer.

Donc, dans votre code, vous devez modifier la signature de votre méthode pour qu'elle ressemble à ceci:

public static void myForEach(List<Integer> list, Consumer<Integer> myBlock) {
   list.forEach(myBlock);
}

Et ensuite, vous devriez pouvoir créer un consommateur, en utilisant une référence de méthode statique, dans votre cas en faisant:

myForEach(theList, Test::displayInt);

En fin de compte, vous pouvez même vous débarrasser de votre méthode myForEach et simplement faire:

theList.forEach(Test::displayInt);

À propos des fonctions en tant que citoyens de première classe

Tout a été dit, la vérité est que Java 8 n'aura pas de fonctions de citoyen de première classe puisqu'un type de fonction structurelle ne sera pas ajouté au langage. Java = offrira simplement un moyen alternatif de créer des implémentations d’interfaces fonctionnelles à partir d’expressions lambda et de références de méthodes. la fonctionnalité existe, car nous pouvons passer des objets en tant que paramètres, les lier à des références de variable et les renvoyer en tant que valeurs d'autres méthodes, ils servent alors presque le même but.

205
Edwin Dalorzo

Lorsque vous devez accepter une fonction en tant qu'argument qui ne prend aucun argument et ne renvoie aucun résultat (void), à mon avis, il est toujours préférable d'avoir quelque chose comme:

  public interface Thunk { void apply(); }

quelque part dans votre code. Dans mes cours de programmation fonctionnelle, le mot "thunk" était utilisé pour décrire de telles fonctions. Pourquoi ce n'est pas dans Java.util.function est au-delà de ma compréhension.

Dans d'autres cas, je constate que même lorsque Java.util.function a quelque chose qui correspond à la signature que je veux, cela ne semble toujours pas correct lorsque la dénomination de l'interface ne correspond pas à l'utilisation de la fonction dans mon code. J'imagine que c'est un point similaire qui est avancé ailleurs ici à propos de 'Runnable' - qui est un terme associé à la classe Thread - donc s'il peut avoir la signature dont j'ai besoin, il risque de confondre le lecteur.

17
LOAS

Je pense que vous devriez utiliser l'interface consommateur au lieu de Function<T, R>.

Un consommateur est fondamentalement une interface fonctionnelle conçue pour accepter une valeur et ne rien renvoyer (c.-à-d. Void)

Dans votre cas, vous pouvez créer un consommateur ailleurs dans votre code, comme suit:

Consumer<Integer> myFunction = x -> {
    System.out.println("processing value: " + x);    
    .... do some more things with "x" which returns nothing...
}

Ensuite, vous pouvez remplacer votre code myForEach par l'extrait ci-dessous:

public static void myForEach(List<Integer> list, Consumer<Integer> myFunction) 
{
  list.forEach(x->myFunction.accept(x));
}

Vous traitez myFunction comme un objet de première classe.

0
Anthony Anyanwu

Définissez le type de retour sur Void au lieu de void et return null

// Modify existing method
public static Void displayInt(Integer i) {
    System.out.println(i);
    return null;
}

OR

// Or use Lambda
myForEach(theList, i -> {System.out.println(i);return null;});
0
Dojo