Chapitre 3 Chaînes, comparateurs et filtres
Certaines méthodes introduites par le JDK sont très utiles pour écrire du code de style fonctionnel. Nous connaissons déjà très bien certaines classes et interfaces de la bibliothèque JDK, comme String. Afin de nous débarrasser de l'ancien style auquel nous sommes habitués, nous devons rechercher activement des opportunités d'utiliser ces nouvelles méthodes. De même, lorsque nous devons utiliser une classe interne anonyme avec une seule méthode, nous pouvons désormais la remplacer par une expression lambda, sans avoir à l'écrire aussi lourdement qu'avant.
Dans ce chapitre, nous utiliserons des expressions lambda et des références de méthodes pour parcourir les chaînes, implémenter l'interface Comparator, afficher les fichiers dans le répertoire et surveiller les modifications apportées aux fichiers et aux répertoires. Certaines des méthodes présentées dans le chapitre précédent continueront d'apparaître ici pour nous aider à mieux accomplir ces tâches. Les nouvelles techniques que vous apprendrez aideront à transformer un code long et fastidieux en quelque chose de concis, rapide à mettre en œuvre et facile à maintenir.
Itérer sur une chaîne
La méthode chars() est une nouvelle méthode de la classe String, qui fait partie de l'interface CharSequence. C'est un outil très utile si vous souhaitez parcourir rapidement la séquence de caractères de String. Avec cet itérateur interne, nous pouvons facilement opérer sur chaque caractère de la chaîne. Essayez d'abord de l'utiliser pour traiter une chaîne. Voici quelques façons d’utiliser les références de méthodes.
Copiez le code comme suit :
chaîne finale str = "w00t" ;
str.chars()
.forEach(ch -> System.out.println(ch));
La méthode chars() renvoie un objet Stream, que nous pouvons utiliser son itérateur interne forEach() pour parcourir. Dans l'itérateur, on peut accéder directement aux caractères de la chaîne. Vous trouverez ci-dessous le résultat de la boucle dans la chaîne et de l’impression de chaque caractère.
Copiez le code comme suit :
119
48
48
116
Ce n’est pas le résultat que nous souhaitons. Nous nous attendons à voir des lettres, mais le résultat est constitué de chiffres. En effet, la méthode chars() renvoie un Stream entier au lieu d'un type de caractère. Comprenons d'abord cette API, puis optimisons les résultats de sortie.
Dans le code précédent, nous avons créé une expression lambda comme paramètre d'entrée de la méthode forEach. Il transmet simplement les paramètres à une méthode println(). Cette opération étant très courante, nous pouvons utiliser le compilateur Java pour simplifier ce code. Tout comme pour l'utilisation des références de méthode à la page 25, remplacez-la par une référence de méthode et laissez le compilateur effectuer le routage des paramètres pour nous.
Nous avons vu comment créer une référence de méthode à une méthode d'instance. Par exemple, pour la méthode name.toUpperCase(), la référence de la méthode est String::toUpperCase. Dans l'exemple suivant, nous appelons une méthode d'instance qui fait référence de manière statique à System.out. Le côté gauche des deux deux-points référencés par la méthode peut être un nom de classe ou une expression. Avec cette flexibilité, nous pouvons facilement créer une référence à la méthode println(), comme ci-dessous.
Copiez le code comme suit :
str.chars()
.forEach(System.out::println);
Comme vous pouvez le constater, le compilateur Java peut effectuer le routage des paramètres de manière très intelligente. Rappelez-vous que les expressions lambda et les références de méthode ne peuvent apparaître que là où une interface fonctionnelle est reçue, et le compilateur Java y générera une méthode correspondante (Annotation : le compilateur générera une implémentation de l'interface fonctionnelle, qui n'a qu'une seule méthode. ). La méthode que nous avons utilisée auparavant fait référence à String::toUpperCase, et les paramètres passés à la méthode générée finiront par devenir l'objet cible de l'appel de méthode, comme ceci : paramètre.toUpperCase(). En effet, la référence de méthode est basée sur le nom de la classe (String). La référence de méthode dans l'exemple ci-dessus est basée sur une expression, qui est une instance de PrintStream et est référencée via System.out. Puisque l'objet de l'appel de méthode existe déjà, le compilateur Java décide d'utiliser les paramètres de la méthode générée comme paramètres de cette méthode println : System.out.println(name).
(Annotation : en fait, il existe principalement deux scénarios. Une référence de méthode est également passée. L'un est l'objet traversé, bien sûr l'objet cible de l'appel de méthode, tel que name.toUpperCase, et l'autre est utilisé comme paramètre de l'appel de méthode, tel que System out.println(name).)
Le code est beaucoup plus simple après avoir utilisé des références de méthodes, mais nous devons mieux comprendre son fonctionnement. Une fois que nous nous sommes familiarisés avec les références de méthodes, nous pouvons déterminer nous-mêmes le routage des paramètres.
Bien que le code de cet exemple soit suffisamment concis, le résultat n’est toujours pas satisfaisant. Nous nous attendions à voir des lettres mais des chiffres sont apparus à la place. Afin de résoudre ce problème, écrivons une méthode pour afficher int sous forme de lettres.
Copiez le code comme suit :
privé statique void printChar (int aChar) {
System.out.println((char)(aChar));
}
L’utilisation de références de méthode peut facilement optimiser les résultats de sortie.
Copiez le code comme suit :
str.chars()
.forEach(IterateString::printChar);
Maintenant, même si le résultat renvoyé par chars() est un entier, cela n'a pas d'importance lorsque nous aurons besoin de l'imprimer, nous le convertirons en caractères. Cette fois, le résultat est enfin des lettres.
Copiez le code comme suit :
w
0
0
t
Si nous voulons traiter des caractères au lieu des entiers depuis le début, nous pouvons directement convertir les entiers en caractères après avoir appelé chars :
Copiez le code comme suit :
str.chars()
.mapToObj(ch -> Character.valueOf((char)ch))
.forEach(System.out::println);
Ici, nous utilisons un itérateur interne du Stream renvoyé par chars(). Bien sûr, plus que cette méthode peut être utilisée. Après avoir obtenu l'objet Stream, nous pouvons utiliser ses méthodes, telles que map(), filter(), réduire(), etc. Nous pouvons utiliser la méthode filter() pour filtrer les caractères qui sont des nombres :
Copiez le code comme suit :
str.chars()
.filter(ch -> Character.isDigit(ch))
.forEach(ch -> printChar(ch));
Lors de la sortie de cette façon, nous ne pouvons voir que des chiffres :
Copiez le code comme suit :
0
0
De même, en plus de transmettre des expressions lambda aux méthodes filter() et forEach(), nous pouvons également utiliser des références de méthode.
Copiez le code comme suit :
str.chars()
.filter(Caractère ::isDigit)
.forEach(IterateString::printChar);
La référence de méthode ici élimine le routage redondant des paramètres. Dans cet exemple, nous constatons également une utilisation différente des deux méthodes précédentes. La première fois que nous avons référencé une méthode d'instance, la deuxième fois, il s'agissait d'une méthode de référence statique (System.out). Cette fois, il s'agit d'une référence à une méthode statique - les références aux méthodes ont payé en silence.
Les références aux méthodes d'instance et aux méthodes statiques se ressemblent toutes : par exemple, String::toUpperCase et Character::isDigit. Le compilateur détermine si la méthode est une méthode d'instance ou une méthode statique pour déterminer comment acheminer les paramètres. S'il s'agit d'une méthode d'instance, elle utilisera les paramètres d'entrée de la méthode générée comme objet cible de l'appel de méthode, tels que le paramètre toUpperCase(); (Bien sûr, il existe des exceptions, telles que l'objet cible de l'appel de méthode. a été spécifié, comme System::out.println ()). De plus, s'il s'agit d'une méthode statique, les paramètres d'entrée de la méthode générée seront utilisés comme paramètres de la méthode référencée, comme Character.isDigit(parameter). L'annexe 2, à la page 152, contient des instructions détaillées sur la façon d'utiliser les références de méthode et leur syntaxe.
Bien que les références de méthodes soient pratiques à utiliser, il existe toujours un problème : l'ambiguïté causée par les conflits de noms de méthodes. Si la méthode de correspondance est à la fois une méthode d'instance et une méthode statique, le compilateur signalera une erreur due à l'ambiguïté de la méthode. Par exemple, si nous écrivons Double::toString comme ceci, nous voulons en fait convertir un type double en chaîne, mais le compilateur ne sait pas s'il doit appeler la méthode d'instance de public String toString() ou appeler public static String toString. (double), car les deux méthodes sont de classe Double. Si vous rencontrez une telle situation, ne vous découragez pas, utilisez simplement des expressions lambda pour la compléter.
Une fois que nous sommes à l'aise avec la programmation fonctionnelle, nous pouvons basculer à volonté entre les expressions lambda et les références de méthode.
Dans cette section, nous utilisons une nouvelle méthode dans Java 8 pour parcourir les chaînes. Jetons un coup d'œil aux améliorations apportées à l'interface du Comparateur.