On cherche à écrire une méthode
namesOfTheAdults qui prend une liste de personnes en paramètre
et renvoie les noms des personnes qui ont 18 ans ou plus.
Sans utiliser l'API des Stream, on va écrire un code qui ressemble à celui-là :
public class Streams {
public record Person(String name, int age) {}
public static List<String> namesOfTheAdults(List<Person> persons) {
var names = new ArrayList<String>();
for(var person: persons) {
if (person.age() >= 18) {
names.add(person.name());
}
}
return names;
}
public static void main(String[] args) {
var persons = List.of(
new Person("Ana", 21),
new Person("John", 17),
new Person("Liv", 29));
var names = namesOfTheAdults(persons);
System.out.println(names);
}
}
On veut simplifier le code de
namesOfTheAdults
-
en créant un Stream sur la liste de personnes,
-
en filtrant (avec filter) pour ne garder que les personnes qui ont plus de 18 ans,
-
en transformant (avec map) pour obtenir le nom des personnes,
-
en renvoyant une liste (avec toList)
On cherche à définir un
Hotel ayant un nom (
name) et une liste de chambres (
rooms).
La liste des chambres est donnée à la création de l’hôtel et il n'est pas possible d'ajouter
des chambres ou de changer le nom de l’hôtel a posteriori.
Une chambre (
Room) possède un nom, un numéro d'étage ainsi qu'un prix.
public record Room(String name, int floor, long price) {
public Room {
Objects.requireNonNull(name, "name is null");
if (floor < 0) {
throw new IllegalArgumentException("floor < 0");
}
if (price <= 0) {
throw new IllegalArgumentException("price <= 0");
}
}
}
Pour tester un hôtel, nous utiliserons le code suivant :
var hotel = new Hotel("paradisio", List.of(
new Room("blue", 100, 100),
new Room("yellow", 110, 200),
new Room("fuchsia", 120, 300),
new Room("red", 100, 200),
new Room("green", 100, 200)
));
-
Écrire le type Hotel en faisant attention à ce que la liste des chambres soit
non mutable après création.
-
Écrire une méthode roomInfo qui renvoie une chaîne de caractères contenant
les noms des chambres en utilisant un Stream et en assurant que le code suivant fonctionne :
System.out.println(hotel.roomInfo()); // blue, yellow, fuchsia, red, green
Note : il existe un Collector nommé joining qui permet de joindre
les chaînes de caractères d'un Stream de sous-types de CharSequence.
-
Écrire une méthode roomInfoSortedByFloor qui renvoie une chaîne de caractère contenant
les noms de chambres triées par le numéro d'étage.
System.out.println(hotel.roomInfoSortedByFloor()); // blue, red, green, yellow, fuchsia
Note : les Stream possèdent une méthode sorted qui prend un Comparator en paramètre. De plus, il existe des méthodes statiques comparing* pour créer des comparateurs à partir d'une fonction qui renvoie la valeur d'un composant d'un record.
Note : attention, le type de floor de Room est un int, donc utiliser la
bonne méthode comparing pour éviter le boxing.
-
Écrire une méthode averagePrice qui renvoie la moyenne des prix de toutes les chambres.
Dans le cas où l’hôtel n'a pas de chambre, on renverra NaN (Not a Number) :
System.out.println(hotel.averagePrice()); // 200.0
Note : pour calculer la moyenne, le plus simple est d' utiliser un Stream de type primitif (IntStream,
LongStream ou DoubleStream) car ce sont des Stream de types numériques
qui possèdent une méthode average.
-
Écrire une méthode roomForPrice1 qui prend en paramètre un prix et renvoie la chambre la plus chère en dessous de ce prix.
S'il y a plusieurs chambres au même prix, on prendra la première.
Cette méthode renvoie un Optional car il peut n'y avoir aucune chambre
qui respecte la contrainte de prix.
En termes d'implantation, on choisit de trier pour accéder à la chambre la plus chère. Par ailleurs, il est plus facile de prendre le premier élément (findFirst) d'un Stream que le dernier (il n'y a pas de méthode pour ça) ; on va donc écrire le comparateur de façon à ce que la chambre
qui a le prix le plus grand soit en premier.
System.out.println(hotel.roomForPrice1(250)); // Optional[Room[name=yellow, floor=110, price=200]]
Note : il existe une méthode reversed sur un comparateur qui permet d'obtenir le comparateur
dans l'ordre inverse (ascendant / descendant).
-
En fait, il existe déjà une méthode max sur les Stream. Écrire une méthode roomForPrice2 qui fonctionne comme roomForPrice1
mais en utilisant la méthode max de Stream.
System.out.println(hotel.roomForPrice2(250)); // Optional[Room[name=yellow, floor=110, price=200]]
Qu'elle implantation est la meilleure ?
-
Écrire une méthode expensiveRoomNames qui prend en paramètre une liste d'hôtels et renvoie les nom des deux (au maximum) chambres les plus chères de chaque hôtel.
var hotel2 = new Hotel("fantastico", List.of(
new Room("queen", 1, 200),
new Room("king", 1, 500)
));
System.out.println(Hotel.expensiveRoomNames(List.of(hotel, hotel2))); // [fuchsia, yellow, king, queen]
Note : comme on veut parcourir tous les hôtels et pour chaque hôtel, parcourir toutes les chambres,
il sera commode d’utiliser flatMap. De plus, il existe une méthode limit pour restreindre le nombre d’éléments qui vont passer par le Stream.
-
On souhaite écrire une méthode roomInfoGroupedByFloor qui renvoie un dictionnaire qui
à chaque étage associe une liste des chambres de cet étage :
System.out.println(hotel.roomInfoGroupedByFloor());
// {100=[Room[name=blue, ...], Room[name=red, ...], Room[name=green, ...]],
120=[Room[name=fuchsia, ...]], 110=[Room[name=yellow, ...]]}
Quel est le type de retour de la méthode roomInfoGroupedByFloor ?
Écrire la code de la méthode roomInfoGroupedByFloor.
Note : il existe un Collector nommé groupingBy.
-
La méthode précédente ne renvoie pas un dictionnaire qui trie les clés, donc les étages ne sont pas forcément dans l'ordre.
En Java, il existe une classe java.util.TreeMap qui maintient les clés triées.
Écrire une méthode roomInfoGroupedByFloorInOrder qui a la même signature que la méthode
précédente mais renvoie un dictionnaire qui stocke les clés de façon triée.
System.out.println(hotel.roomInfoGroupedByFloorInOrder());
// {100=[Room[name=blue, ...], Room[name=red, ...], Room[name=green, ...]],
110=[Room[name=yellow, ...]], 120=[Room[name=fuchsia, ...]]}
Note : regardez comment utiliser la méthode groupingBy à 3 paramètres.