Modern Java Engineering
Chapitre 3
Chapitre 03 : Java 8-17-21
Chapitre 03 : Java 8-17-21
Cours : Java 8-17-21
1. Lambdas et Interfaces Fonctionnelles
1.1 Syntaxe des Lambdas
Une lambda est une fonction anonyme qui peut être traitée comme une valeur.
// Syntaxe complète
(paramètres) -> { corps }
// Exemples
(int a, int b) -> { return a + b; }
(String s) -> { System.out.println(s); }
() -> { return 42; }
// Syntaxe simplifiée
(a, b) -> a + b // Types inférés, return implicite
s -> System.out.println(s) // Un seul paramètre : parenthèses optionnelles
() -> 42 // Aucun paramètre
x -> x * 2 // Expression simple
1.2 Interfaces Fonctionnelles
Une interface fonctionnelle a exactement une méthode abstraite. L'annotation @FunctionalInterface est optionnelle mais recommandée.
@FunctionalInterface
interface Calcul {
int apply(int a, int b);
}
// Utilisation
Calcul addition = (a, b) -> a + b;
Calcul multiplication = (a, b) -> a * b;
1.3 Les 4 Interfaces Fonctionnelles Principales
// 1. Predicate<T> → boolean test(T t)
Predicate<String> nonVide = s -> !s.isEmpty();
Predicate<Integer> pair = n -> n % 2 == 0;
// 2. Function<T, R> → R apply(T t)
Function<String, Integer> longueur = String::length;
Function<Integer, String> toString = Object::toString;
// 3. Consumer<T> → void accept(T t)
Consumer<String> afficher = System.out::println;
Consumer<List<String>> vider = List::clear;
// 4. Supplier<T> → T get()
Supplier<Double> random = Math::random;
Supplier<LocalDate> aujourdHui = LocalDate::now;
1.4 Interfaces Spécialisées
// Primitives (évitent l'autoboxing)
IntFunction<String> intToString = String::valueOf;
ToIntFunction<String> toLength = String::length;
IntPredicate estPair = n -> n % 2 == 0;
IntConsumer afficherInt = System.out::println;
IntSupplier supplierInt = () -> 42;
// Binaires
BinaryOperator<Integer> sum = Integer::sum;
BiFunction<String, String, String> concat = String::concat;
BiPredicate<String, String> egales = String::equals;
BiConsumer<String, Integer> printNTimes = (s, n) -> {
for (int i = 0; i < n; i++) System.out.println(s);
};
1.5 Method References
// Static method : Class::staticMethod
Function<String, Integer> parser = Integer::parseInt; // s -> Integer.parseInt(s)
// Instance method of object : object::instanceMethod
var list = List.of("a", "b", "c");
Consumer<String> printer = System.out::println;
// Instance method of class : Class::instanceMethod
Function<String, Integer> length = String::length;
// Constructor : Class::new
Supplier<List<String>> listCreator = ArrayList::new;
Function<String, StringBuilder> sbCreator = StringBuilder::new;
2. Streams API
2.1 Pipeline Pattern
Un Stream a 3 phases :
- Source : collection, tableau, générateur
- Opérations intermédiaires : filter, map, sorted (lazy)
- Opération terminale : collect, forEach, count (déclenche le traitement)
List<String> result = list.stream() // 1. Source
.filter(s -> !s.isEmpty()) // 2. Intermédiaire (lazy)
.map(String::toUpperCase) // 2. Intermédiaire (lazy)
.sorted() // 2. Intermédiaire (lazy)
.collect(Collectors.toList()); // 3. Terminale (déclenche)
2.2 Création de Streams
// Depuis une collection
list.stream();
list.parallelStream();
// Depuis un tableau
Arrays.stream(array);
Stream.of("a", "b", "c");
// Depuis des valeurs
Stream.of(1, 2, 3, 4, 5);
Stream.concat(stream1, stream2);
// Streams infinis
Stream.generate(Math::random) // infini
.limit(10); // limité
Stream.iterate(0, n -> n + 2) // infini
.limit(10);
// Java 9+ : iterate avec condition
Stream.iterate(0, n -> n < 100, n -> n + 2);
// Java 9+ : ofNullable
Stream.ofNullable(getValue()); // Stream vide ou 1 élément
// Primitives
IntStream.range(0, 10); // 0..9
IntStream.rangeClosed(1, 10); // 1..10
IntStream.iterate(0, i -> i + 1).limit(100);
LongStream.range(0L, 1_000_000L);
DoubleStream.generate(Math::random).limit(10);
2.3 Opérations Intermédiaires
// filter : garde les éléments correspondant au prédicat
stream.filter(s -> s.length() > 5)
// map : transforme chaque élément
stream.map(String::toUpperCase)
stream.mapToInt(String::length)
stream.mapToLong(...)
stream.mapToDouble(...)
// flatMap : aplatit les Streams imbriqués
List<List<String>> listOfLists = ...;
listOfLists.stream()
.flatMap(Collection::stream) // Stream<String>
.toList();
// flatMap pour Optionals (Java 16+)
stream.flatMap(Optional::stream)
// distinct : éléments uniques (basé sur equals)
stream.distinct()
// sorted : tri (naturel ou Comparator)
stream.sorted()
stream.sorted(Comparator.reverseOrder())
stream.sorted(Comparator.comparing(Person::age))
// peek : debug (ne pas utiliser pour modifier)
stream.peek(System.out::println)
// limit / skip
stream.limit(10) // 10 premiers
stream.skip(5) // ignore 5 premiers
// takeWhile / dropWhile (Java 9+)
stream.takeWhile(n -> n < 10) // prend tant que condition vraie
stream.dropWhile(n -> n < 10) // ignore tant que condition vraie
// mapMulti (Java 16+) : alternative à flatMap
stream.mapMulti((String s, Consumer<String> downstream) -> {
downstream.accept(s.toLowerCase());
downstream.accept(s.toUpperCase());
});
2.4 Opérations Terminales
// forEach / forEachOrdered
stream.forEach(System.out::println); // ordre non garanti
stream.forEachOrdered(System.out::println); // ordre garanti
// collect
List<String> list = stream.collect(Collectors.toList());
Set<String> set = stream.collect(Collectors.toSet());
Map<Integer, String> map = stream.collect(Collectors.toMap(String::length, Function.identity()));
// toList (Java 16+) : immuable
List<String> list = stream.toList();
// toArray
String[] array = stream.toArray(String[]::new);
// reduce
Optional<Integer> sum = stream.reduce(Integer::sum);
int sum = stream.reduce(0, Integer::sum); // avec valeur initiale
// count
long count = stream.count();
// anyMatch / allMatch / noneMatch
boolean hasLong = stream.anyMatch(s -> s.length() > 10);
boolean allLong = stream.allMatch(s -> s.length() > 10);
boolean noneLong = stream.noneMatch(s -> s.length() > 10);
// findFirst / findAny
Optional<String> first = stream.findFirst(); // premier élément
Optional<String> any = stream.findAny(); // n'importe lequel (parallel)
// min / max
Optional<String> min = stream.min(Comparator.naturalOrder());
Optional<String> max = stream.max(String::compareTo);
2.5 flatMap Approfondi
// flatMap avec Optional (Java 9+)
public List<String> getEmails(List<User> users) {
return users.stream()
.map(User::getEmail) // Stream<Optional<String>>
.flatMap(Optional::stream) // Java 9+ : Stream<String>
.toList();
}
// flatMap avec objets imbriqués
List<String> allTags = posts.stream() // Stream<Post>
.map(Post::getTags) // Stream<List<String>>
.flatMap(List::stream) // Stream<String>
.distinct()
.toList();
// flatMap avec validation
List<String> validItems = items.stream()
.flatMap(item -> {
if (item.isValid()) return Stream.of(item);
else return Stream.empty();
})
.map(Item::getName)
.toList();
3. Collectors et Opérations Terminales
3.1 Collectors Essentiels
// Collections
.toList() // Java 16+ : liste immuable
Collectors.toList() // liste mutable
Collectors.toSet() // set
Collectors.toCollection(LinkedList::new) // collection spécifique
// Map
Collectors.toMap(Function.identity(), String::length)
Collectors.toMap(k -> k, v -> v, (v1, v2) -> v1) // gestion doublons
Collectors.toMap(k -> k, v -> v, (v1, v2) -> v1, TreeMap::new)
// Regroupement
Collectors.groupingBy(String::length) // Map<Integer, List<String>>
Collectors.groupingBy(String::length, Collectors.toSet())
Collectors.groupingBy(String::length, TreeMap::new, Collectors.toList())
// Partitionnement (2 groupes)
Collectors.partitioningBy(s -> s.length() > 5) // Map<Boolean, List<String>>
Collectors.partitioningBy(s -> s.length() > 5, Collectors.counting())
// Jointure
Collectors.joining() // "abc"
Collectors.joining(", ") // "a, b, c"
Collectors.joining(", ", "[", "]") // "[a, b, c]"
// Statistiques
Collectors.summarizingInt(String::length) // IntSummaryStatistics
Collectors.summingInt(String::length) // int
Collectors.averagingInt(String::length) // double
Collectors.counting() // long
// Mapping et Reducing
Collectors.mapping(String::toUpperCase, Collectors.toList())
Collectors.filtering(s -> s.length() > 5, Collectors.toList()) // Java 9+
Collectors.flatMapping(s -> s.chars().boxed(), Collectors.toList()) // Java 9+
Collectors.reducing(0, String::length, Integer::sum)
3.2 Collectors Personnalisés
// Collecteur personnalisé simple
List<String> result = stream.collect(
ArrayList::new, // supplier
ArrayList::add, // accumulator
ArrayList::addAll // combiner (parallel)
);
// Collecteur avec statistiques
public static <T> Collector<T, ?, Map<Boolean, List<T>>> partitionCollector(
Predicate<? super T> predicate) {
return Collectors.partitioningBy(predicate);
}
4. Parallel Streams et Performance
4.1 Quand utiliser parallelStream ?
// ✅ Bon candidat : calculs intensifs, grandes collections, indépendance
long sum = LongStream.range(0, 10_000_000)
.parallel()
.sum();
// ❌ Mauvais candidat : petites collections, blocage I/O, état partagé
list.stream().parallel().collect(toList()); // Séquentiel est plus rapide
// ⚠️ Opérations avec état partagé (non thread-safe)
List<Integer> result = new ArrayList<>(); // ❌ Pas thread-safe!
IntStream.range(0, 100).parallel()
.forEach(result::add); // Résultat indéterministe
4.2 Facteurs de Performance
// Taille de la collection
// Le parallélisme a un overhead. Pas rentable pour < 10 000 éléments.
// Type d'opération
// CPU-bound (calcul) : bon candidat pour parallélisme
// I/O-bound (réseau/fichier) : mieux avec CompletableFuture
// ForkJoinPool commun
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "4");
// Par défaut : nombre de processeurs - 1
4.3 Opérations de l'ordre
// Opérations qui brisent l'ordre
List<Integer> unordered = stream.parallel()
.unordered() // Indique que l'ordre n'importe pas → meilleures performances
.distinct()
.toList();
4.4 Bonnes Pratiques Performance
// ✅ Utiliser les primitives (évite l'autoboxing)
IntStream.range(0, 1000).sum();
// ❌ Autoboxing coûteux
Stream.iterate(0, i -> i + 1).limit(1000).mapToInt(i -> i).sum();
// ✅ flatMap coûteux → mapMulti (Java 16+)
stream.<String>mapMulti((s, consumer) -> {
for (String word : s.split(" ")) consumer.accept(word);
});
// ✅ Utiliser findFirst → findAny en parallèle
stream.parallel().findAny(); // plus rapide que findFirst()
// ⚠️ Les opérations avec état (sorted, distinct, limit) sont plus coûteuses en parallèle
5. Optional Patterns Avancés
5.1 Composition d'Optionals
// or (Java 9+) : Optional alternatif
Optional<String> result = findPrimaryEmail(user)
.or(() -> findSecondaryEmail(user))
.or(() -> Optional.of("default@email.com"));
// Chaînage avec flatMap
Optional<String> city = findUser(userId)
.flatMap(User::getAddress)
.map(Address::getCity);
// ifPresentOrElse (Java 9+)
opt.ifPresentOrElse(
value -> System.out.println("Présent: " + value),
() -> System.out.println("Absent")
);
5.2 Optional dans les Streams
// Java 9+ : Optional::stream dans flatMap
List<String> emails = users.stream()
.map(User::getEmail) // Stream<Optional<String>>
.flatMap(Optional::stream) // Stream<String>
.toList();
// Java 16+ : mapMulti équivalent
List<String> emails = users.stream()
.<String>mapMulti((user, consumer) ->
user.getEmail().ifPresent(consumer))
.toList();
5.3 Combinators avec Optional
// Combiner deux Optionals
public static <T, U, R> Optional<R> combine(
Optional<T> opt1,
Optional<U> opt2,
BiFunction<T, U, R> combiner) {
return opt1.flatMap(t -> opt2.map(u -> combiner.apply(t, u)));
}
// Usage
var result = combine(
Optional.of("Hello"),
Optional.of(5),
(s, n) -> s + " " + n
); // Optional["Hello 5"]
// Optional avec API stream
public static <T> Optional<T> findFirst(List<Optional<T>> optionals) {
return optionals.stream()
.flatMap(Optional::stream)
.findFirst();
}
6. Switch Moderne (Arrow, Pattern Matching)
6.1 Arrow Switch (Java 14+)
// Expression switch avec ->
String result = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> "6h de sommeil";
case TUESDAY -> "7h de sommeil";
case THURSDAY, SATURDAY -> "8h de sommeil";
case WEDNESDAY -> "9h de sommeil";
default -> "Pas un jour valide";
};
// Avec yield pour les blocs
String result = switch (day) {
case MONDAY -> {
System.out.println("Lundi!");
yield "Début de semaine";
}
case FRIDAY -> {
System.out.println("Vendredi!");
yield "Fin de semaine";
}
default -> "Autre jour";
};
6.2 Pattern Matching pour Switch (Java 21+)
// Avant Java 21 : if/else chaîne
String formatValue(Object obj) {
if (obj instanceof Integer i) return "int: " + i;
else if (obj instanceof String s) return "String: " + s;
else if (obj instanceof Long l) return "long: " + l;
else return "inconnu";
}
// Java 21+ : pattern matching for switch
String formatValue(Object obj) {
return switch (obj) {
case Integer i -> "int: " + i;
case String s -> "String: " + s;
case Long l -> "long: " + l;
case null -> "Null!";
default -> "inconnu";
};
}
// Guards (clause when)
String describe(Object obj) {
return switch (obj) {
case Integer i when i < 0 -> "entier négatif";
case Integer i when i == 0 -> "zéro";
case Integer i -> "entier positif: " + i;
case String s when s.length() > 10 -> "longue chaîne";
case String s -> "courte chaîne: " + s;
case null, default -> "autre";
};
}
6.3 Record Patterns (Java 21+)
record Point(int x, int y) {}
record Line(Point start, Point end) {}
// Déconstruction de records dans switch
void print(Object obj) {
switch (obj) {
case Point(int x, int y) -> System.out.println("Point: " + x + "," + y);
case Line(Point s, Point e) -> System.out.println("Line: " + s + " → " + e);
default -> System.out.println("autre");
}
}
// Avec guards
void analyze(Point p) {
switch (p) {
case Point(int x, int y) when x == y -> System.out.println("Sur la diagonale");
case Point(int x, int y) when x > 0 && y > 0 -> System.out.println("Quadrant 1");
case Point(var x, var y) -> System.out.println("Autre point");
}
}
7. Text Blocks (Java 15+)
7.1 Syntaxe
// Multi-lignes automatique, échappement minimal
String json = """
{
"name": "Java",
"version": 21,
"features": [
"Records",
"Sealed Classes",
"Pattern Matching"
]
}
""";
String html = """
<html>
<body>
<h1>%s</h1>
<p>%s</p>
</body>
</html>
""".formatted("Titre", "Contenu");
// SQL
String query = """
SELECT u.name, u.email, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.status = 'ACTIVE'
ORDER BY o.total DESC
LIMIT 10
""";
7.2 Formatage et Échappement
// formattage avec String.formatted()
String message = """
Bonjour %s !
Votre commande #%d est confirmée.
Montant total : %.2f €
""".formatted("Alice", 12345, 99.99);
// Échappement : \s = espace insécable
// \ = fin de ligne (permet de continuer)
String longText = """
Ceci est un texte très long que je \
souhaite écrire sur plusieurs lignes \
dans le code source mais sur une seule \
ligne dans l'exécution.
""";
// Indentation automatique
String code = """
public void hello() {
System.out.println("Hello!");
}
""";
// L'indentation est déterminée par la position du """ fermant
8. Records et Sealed Classes
8.1 Records : Rappels et Usage Avancé
// Record avec constructeur compact et validation
public record Email(String value) {
// Validé avant affectation
public Email {
if (value == null || !value.contains("@")) {
throw new IllegalArgumentException("Email invalide");
}
}
// Méthode statique
public static Email of(String value) {
return new Email(value);
}
}
// Record avec méthode
public record Range(int min, int max) {
public Range {
if (min > max) throw new IllegalArgumentException("min > max");
}
public boolean contains(int value) {
return value >= min && value <= max;
}
}
// Record local (Java 16+)
public List<String> process(List<Person> people) {
record NameCount(String name, long count) {}
return people.stream()
.collect(Collectors.groupingBy(Person::name, Collectors.counting()))
.entrySet().stream()
.map(e -> new NameCount(e.getKey(), e.getValue()))
.sorted(Comparator.comparingLong(NameCount::count).reversed())
.map(NameCount::name)
.toList();
}
8.2 Sealed Classes
// Hiérarchie d'expressions mathématiques
public sealed interface Expr
permits Const, Add, Mul, Neg, Div {}
public record Const(double value) implements Expr {}
public record Add(Expr left, Expr right) implements Expr {}
public record Mul(Expr left, Expr right) implements Expr {}
public record Div(Expr left, Expr right) implements Expr {
public Div {
if (right instanceof Const(double v) && v == 0) {
throw new ArithmeticException("Division by zero");
}
}
}
public record Neg(Expr expr) implements Expr {}
// Évaluation avec pattern matching exhaustif
public static double eval(Expr e) {
return switch (e) {
case Const(double v) -> v;
case Add(Expr l, Expr r) -> eval(l) + eval(r);
case Mul(Expr l, Expr r) -> eval(l) * eval(r);
case Div(Expr l, Expr r) -> eval(l) / eval(r);
case Neg(Expr x) -> -eval(x);
// Pas de default : exhaustif !
};
}
9. Pattern Matching
9.1 instanceof (Java 16+)
// Avant
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}
// Après (Java 16+)
if (obj instanceof String s) {
System.out.println(s.length());
}
// Avec condition composée
if (obj instanceof String s && s.length() > 5) {
System.out.println(s.toUpperCase());
}
9.2 Record Patterns (Java 21+)
record Point(int x, int y) {}
record Rectangle(Point topLeft, Point bottomRight) {}
// Déconstruction
void printPoint(Object obj) {
if (obj instanceof Point(int x, int y)) {
System.out.println(x + ", " + y);
}
}
// Déconstruction imbriquée
void printArea(Object obj) {
if (obj instanceof Rectangle(Point(int x1, int y1), Point(int x2, int y2))) {
int area = (x2 - x1) * (y2 - y1);
System.out.println("Area: " + area);
}
}
// Avec var (type inféré)
void printCoords(Object obj) {
if (obj instanceof Point(var x, var y)) {
System.out.println(x + ", " + y);
}
}
10. Virtual Threads (Project Loom)
10.1 Principe
Les Virtual Threads (Java 21+) sont des threads légers gérés par la JVM, pas par l'OS.
// Creer un virtual thread
Thread vThread = Thread.startVirtualThread(() -> {
System.out.println("Virtual thread: " + Thread.currentThread());
});
// Avec ExecutorService (try-with-resources, Java 19+)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
int taskId = i;
futures.add(executor.submit(() -> handleTask(taskId)));
}
}
// 1 million de threads légers
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 1_000_000)
.forEach(i -> executor.submit(() -> {
Thread.sleep(1000);
return i;
}));
} // Tout se termine en ~1 seconde
10.2 Virtual Threads vs Platform Threads
// Platform thread : coûteux (~1 Mo de stack)
Thread.ofPlatform().start(() -> { ... });
// Virtual thread : léger (~quelques Ko)
Thread.ofVirtual().start(() -> { ... });
// Différence de performance
long start = System.nanoTime();
// Platform threads : maximum ~4000 avant OOM
try (var executor = Executors.newFixedThreadPool(1000)) {
// ...
}
// Virtual threads : 1 000 000+ sans problème
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
// ...
}
10.3 Bonnes Pratiques
// ✅ Adapter le code existant : remplacer les pools par virtual threads
// ❌ Éviter : synchronized blocks (pinnage)
synchronized (lock) { // Le thread virtuel pince le thread porteur
Thread.sleep(100); // Bloque le thread porteur !
}
// ✅ Utiliser ReentrantLock à la place
private final Lock lock = new ReentrantLock();
lock.lock();
try {
Thread.sleep(100); // Ne bloque pas le thread porteur
} finally {
lock.unlock();
}
// ✅ Pool de threads pour les tâches CPU-bound
try (var executor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors())) {
// Tâches intensives en calcul
}
// ✅ Virtual threads pour les tâches I/O-bound
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
// Tâches réseau, fichier, base de données
}
// ⚠️ ThreadLocal : à éviter (trop de threads)
// ⚠️ ScopedValue (Java 21+) : alternative à privilégier
10.4 Structured Concurrency (Preview Java 21+)
// Exécution structurée : portée définie
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> user = scope.fork(() -> fetchUser());
Future<Integer> orders = scope.fork(() -> fetchOrders());
scope.join(); // Attend toutes les tâches
scope.throwIfFailed(); // Lance si une tâche échoue
return new UserData(user.resultNow(), orders.resultNow());
}
11. Résumé
| Feature | Java | Statut |
|---|---|---|
| Lambdas | 8 | Définitif |
| Streams | 8 | Définitif |
| Optional | 8 | Définitif |
| Modules (JPMS) | 9 | Définitif |
| try-with-resources amélioré | 9 | Définitif |
| Var | 10 | Définitif |
| Text Blocks | 15 | Définitif |
| Records | 16 | Définitif |
| Pattern Matching instanceof | 16 | Définitif |
| Sealed Classes | 17 | Définitif |
| Pattern Matching switch | 21 | Définitif |
| Record Patterns | 21 | Définitif |
| Virtual Threads | 21 | Définitif |
| Structured Concurrency | 21 | Preview |
| Scoped Values | 21 | Preview |