Avant de voir comment utiliser les Stream pour transformer les éléments d'une liste à l'aide de la méthode map, regardons d'abord comment procéder sans les utiliser.
Partons de la liste de personnes suivante :
List<Person> list = new ArrayList<>(Arrays.asList(
new Person("Kari", "Parmentier"),
new Person("Gérard", "Laurent"),
new Person("Apolline", "Truchon")
));Tu veux concaténer les attributs de chaque Person. Il va donc falloir parcourir la liste :
List<String> listMapped = new ArrayList<>();
for (Person item : list) {
listMapped.add(item.getFirstName() + " " + item.getLastName());
}Si tu affiches le contenu de la nouvelle liste transformée, tu obtiens le résultat suivant :
System.out.println(listMapped.toString());[Kari Parmentier, Gérard Laurent, Apolline Truchon]
Maintenant, tu peux isoler dans une méthode le code permettant de transformer les éléments, de la façon suivante :
private static List<String> map(List<Person> list) {
List<String> listMapped = new ArrayList<>();
for (Person item : list) {
listMapped.add(item.getFirstName() + " " + item.getLastName());
}
return listMapped;
}Tu pourras désormais transformer n'importe quelle liste de personnes de la façon suivante :
List<String> listMapped = map(list);Dans le cas que tu as vu précédemment, la transformation est contenue dans la méthode map. Il s'agit de la ligne suivante :
item.getFirstName() + " " + item.getLastName()Imaginons que tu veuilles améliorer ta méthode afin de lui passer en paramètre la transformation à effectuer. Tu pourrais, par exemple, utiliser la même méthode map pour ne récupérer que le nom et le mettre en majuscules.
Il va te falloir appliquer une fonction de transformation à chacun des éléments. En Java, on résout cette problématique en créant une interface.
Ton interface va permettre de concaténer les valeurs des attributs firstName et lastName d'une instance de Person. Pour cet exemple, elle va s'appeler Concat.
Cette interface ne va contenir qu'une seule signature de méthode : apply, qui appliquera la transformation.
private interface Concat {
String apply(Person item);
}Comme il s'agit d'une interface, on ne renseigne pas de corps pour la méthode. C'est logique : la transformation à appliquer dépendra de la liste. Comme indiqué plus haut, on pourrait vouloir ne conserver que le nom, uniquement le prénom ou transformer l'un d'entre eux en majuscules.
Il va te falloir modifier ta méthode map afin qu'elle accepte en argument une instance de l'interface Concat :
private static List<String> map(List<Person> list, Concat concat) {
List<String> listMapped = new ArrayList<>();
for (Person item : list) {
listMapped.add(concat.apply(item));
}
return listMapped;
}Pour appeler la méthode map, tu vas créer une classe anonyme, qui implémente une interface tout en en créant une instance.
Tu peux alors passer la classe anonyme en paramètre de la méthode map :
List<String> listMapped = map(list, new Concat() {
@Override
public String apply(Person item) {
return item.getFirstName() + " " + item.getLastName();
}
});Depuis Java 8, il est possible d'utiliser un certain nombre d'interfaces fonctionnelles déjà existantes. Dans le cas d'une transformation, tu vas avoir besoin du type des éléments à transformer et du type de l'élément obtenu après la transformation. Ces deux types peuvent être différents : dans notre exemple, on part d'une Person pour obtenir une String.
Tu vas donc avoir besoin de l'interface fonctionnelle Function :
@FunctionalInterface
public interface Function<T, R> {
R apply(T var1);
// few other default methods here
}Ici,
TetRreprésentent des classes génériques, qui peuvent correspondre à n'importe quel objet.
Test le type de l'élément à transformerRest le type obtenu après la transformation
Comme tu peux le remarquer, elle est presque identique à l'interface Concat créée précédemment. Tu peux donc créer une classe anonyme à sa place, de la façon suivante :
List<String> listMapped = map(list, new Function<Person, String>() {
@Override
public String apply(Person item) {
return item.getFirstName() + " " + item.getLastName();
}
});Cependant, comme ta méthode map s'attend à recevoir un objet de type Concat, il va également falloir remplacer cet argument par une Function :
private static List<String> map(List<Person> list, Function<Person, String> concat) {
List<String> listMapped = new ArrayList<>();
for (Person item : list) {
listMapped.add(concat.apply(item));
}
return listMapped;
}Tu peux utiliser des lambdas lors de l'appel d'une interface fonctionnelle.
Voici l'appel actuel de ta Function :
new Function<Person, String>() {
@Override
public String apply(Person item) {
return item.getFirstName() + " " + item.getLastName();
}
}Rappelle-toi qu'un lambda se présente sous la forme suivante :
(arguments, ...) -> {
// body
}Ici, tu peux donc modifier le code de la façon suivante :
(item) -> {
return item.getFirstName() + " " + item.getLastName();
}Il est possible de le simplifier davantage ! Pour rappel, si une méthode ne possède qu'un seul argument, les lambdas permettent de supprimer les parenthèses. De plus, si le corps de la méthode ne contient que le retour d'une expression, il est possible de supprimer les accolades et le mot-clé return : le retour sera effectué de manière implicite.
item -> item.getFirstName() + " " + item.getLastName()Il est alors possible de simplifier l'appel de map afin d'obtenir le code suivant :
List<String> listMapped = map(list, item -> item.getFirstName() + " " + item.getLastName());