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

143

Je joue avec Java 8 pour découvrir comment fonctionne en tant que 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, c'est de passer la méthode displayIntà la méthode en myForEachutilisant une référence de méthode. Le compilateur génère 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 s'en plaint void cannot be converted to Void. Je ne sais pas comment spécifier le type de l'interface de fonction dans la signature de myForEachtelle sorte que le code se compile. Je sais que je pourrais simplement changer le type de retour de displayIntà Void, puis revenir null. Cependant, il peut y avoir des situations où il n'est pas possible de modifier la méthode que je veux passer ailleurs. Existe-t-il un moyen simple de réutiliser displayInttel quel?

Valentin
la source

Réponses:

244

Vous essayez d'utiliser le mauvais type d'interface. Le type Function 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 devez utiliser Consumer (anciennement appelé Block)

Le type de fonction est déclaré comme

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

Cependant, le type Consumer est compatible avec celui 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 (nul). Et c'est ce que tu veux.

Par exemple, si je voulais afficher tous les éléments d'une liste, je pourrais 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 pas de 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 renvoie 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 de méthode d'objet en utilisant la printlnméthode dans l' System.outobjet, comme ceci:

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

Ou je pourrais simplement faire

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

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

Donc, dans votre code, vous devez changer la signature de votre méthode en un peu comme:

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

Et puis vous devriez être en mesure de 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 myForEachcomplètement de votre méthode et simplement faire:

theList.forEach(Test::displayInt);

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

Cela dit, la vérité est que Java 8 n'aura pas de fonctions en tant que citoyens de première classe car 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. En fin de compte, les expressions lambda et les références de méthode seront liées aux références d'objet, donc tout ce que nous avons, ce sont des objets en tant que citoyens de première classe. L'important est que la fonctionnalité soit là car nous pouvons passer des objets en tant que paramètres, les lier à des références de variables et les renvoyer en tant que valeurs d'autres méthodes, alors ils servent à peu près un objectif similaire.

Edwin Dalorzo
la source
1
Au fait, a Blockété changé pour les Consumerdernières versions de l'API JDK 8
Edwin Dalorzo
4
Le dernier paragraphe passe sous silence certains faits importants: les expressions Lambda et les références de méthode sont plus qu'un ajout syntaxique. La JVM elle-même fournit de nouveaux types pour gérer les références de méthode plus efficacement qu'avec les classes anonymes (le plus important MethodHandle), et en utilisant cela, le compilateur Java est capable de produire du code plus efficace, par exemple en implémentant des lambdas sans fermeture en tant que méthodes statiques, empêchant ainsi la génération de classes supplémentaires.
Feuermurmel
7
Et la version correcte de Function<Void, Void>est Runnable.
OrangeDog du
2
@OrangeDog Ce n'est pas totalement vrai. Dans les commentaires Runnableet les Callableinterfaces, il est écrit qu'ils sont censés être utilisés en conjonction avec des threads . La conséquence ennuyeuse de cela est que certains outils d'analyse statique (comme Sonar) se plaindront si vous appelez la run()méthode directement.
Denis
Venant purement d'un arrière-plan Java, je me demande quels sont en particulier les cas où Java ne peut pas traiter les fonctions comme des citoyens de première classe même après l'introduction d'interfaces fonctionnelles et de lambdas depuis Java8?
Vivek Sethi le
21

Lorsque vous devez accepter une fonction comme 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» a été utilisé pour décrire ces fonctions. Pourquoi ce n'est pas dans java.util.function est au-delà de ma compréhension.

Dans d'autres cas, je trouve que même lorsque java.util.function a quelque chose qui correspond à la signature que je veux - cela ne me semble toujours pas correct lorsque le nom de l'interface ne correspond pas à l'utilisation de la fonction dans mon code. Je suppose que c'est un point similaire qui est fait ailleurs ici concernant 'Runnable' - qui est un terme associé à la classe Thread - donc bien qu'il puisse avoir la signature dont j'ai besoin, il est toujours susceptible de dérouter le lecteur.

LOAS
la source
En particulier, Runnablen'autorise pas les exceptions vérifiées, ce qui le rend inutile pour de nombreuses applications. Comme ce Callable<Void>n'est pas utile non plus, vous devez définir le vôtre semble-t-il. Laid.
Jesse Glick
En référence au problème d'exception vérifié, les nouvelles fonctions de Java ont généralement du mal avec cela. c'est un énorme bloqueur pour certaines choses comme l'API Stream. Très mauvaise conception de leur part.
user2223059
0

Définir le type de retour sur au Voidlieu de voidetreturn null

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

OU

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

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

Un consommateur est essentiellement une interface fonctionnelle conçue pour accepter une valeur et ne rien renvoyer (c'est-à-dire vide)

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

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

Ensuite, vous pouvez remplacer votre myForEachcode 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.

Anthony Anyanwu
la source
2
Qu'est-ce que cela ajoute au-delà des réponses existantes?
Matthew Lire