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