:: Enseignements :: Licence :: L3 :: 2025-2026 :: Programmation Objet avec Java ::
![[LOGO]](http://monge.univ-eiffel.fr/ens/resources/mlv.png) |
Stream, Collecteur et Comparateur
|
Exercice 1 - Un exemple simple
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.
Si on n'utilise pas l'API des Stream, on va écrire un code qui ressemble à celui-là :
dans le fichier
Person.java
public record Person(String name, int age) {}
et dans le fichier
Streams.java
public class Streams {
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)
Quelle est l'interface fonctionnelle prise en paramètre de filter ?
Quels sont les types de paramètre et de retour de la lambda ?
Quelle est l'interface fonctionnelle prise en paramètre de map ?
Quels sont les types de paramètre et de retour de la lambda ?
Écrire une nouvelle version de namesOfTheAdults en utilisant l'API des streams.
On veut maintenant écrire une méthode static sumAge qui prend une liste de Person et
qui renvoie un long donnant la somme des ages d'une personne dont le nom commence par la lettre "C".
Si il n'y a aucune personne dont le nom commence par "C", on renverra 0.
Écrire la méthode long sumAge(List<Person> persons) en utilisant l'API des streams.
Exercice 2 - Le grand Hôtel
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 modifiable 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 en termes de complexité pire cas ?
Écrire une méthode
expensiveRoomNames qui prend en paramètre une liste d'hôtels et
renvoie une liste des noms 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 est 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 qui 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 dans la javadoc comment utiliser la méthode
groupingBy à 3 paramètres.
© Université de Marne-la-Vallée