Java 8 est sans doute la version la plus importante de l'histoire du langage depuis Java 5. Sorti en mars 2014, il a introduit la programmation fonctionnelle dans Java, transformant radicalement la façon d'écrire du code. Dix ans plus tard, ses fonctionnalités restent au cœur du développement Java quotidien.
1. Expressions Lambda
Les expressions lambda permettent de traiter les fonctions comme des valeurs de première classe. Elles éliminent le boilerplate des classes anonymes.
// ❌ Avant Java 8 — classe anonyme verbeux
Comparator<String> comp = new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
};
// ✅ Java 8 — lambda concis
Comparator<String> comp = (a, b) -> a.compareTo(b);
// Utilisation dans un tri
List<String> noms = Arrays.asList("Charlie", "Alice", "Bob");
noms.sort((a, b) -> a.compareTo(b));
// Runnable sans argument
Runnable r = () -> System.out.println("Hello lambda!");
2. Stream API
L'API Stream est la killer feature de Java 8. Elle permet de traiter des collections de façon déclarative, en enchaînant des opérations fonctionnelles.
List<String> developpeurs = List.of(
"Alice", "Bob", "Charlie", "David", "Eve"
);
// Filtrer, transformer, collecter
List<String> resultat = developpeurs.stream()
.filter(n -> n.length() > 3) // filtrage
.map(String::toUpperCase) // transformation
.sorted() // tri
.collect(Collectors.toList()); // collecte
// Agrégation
OptionalInt max = IntStream.of(3, 1, 4, 1, 5, 9)
.filter(n -> n % 2 == 0)
.max();
// Groupement
Map<Integer, List<String>> parLongueur = developpeurs.stream()
.collect(Collectors.groupingBy(String::length));
// Traitement parallèle — gratuit !
long count = developpeurs.parallelStream()
.filter(n -> n.startsWith("A"))
.count();
3. Interfaces fonctionnelles & java.util.function
Java 8 introduit le package java.util.function avec des interfaces fonctionnelles prêtes à l'emploi.
// Function<T, R> — transforme T en R
Function<String, Integer> longueur = String::length;
Function<String, String> upper = String::toUpperCase;
Function<String, String> compose = longueur.andThen(i -> i + " chars");
// Predicate<T> — teste une condition
Predicate<String> estVide = String::isEmpty;
Predicate<String> nonVide = estVide.negate();
Predicate<String> longNonVide = nonVide.and(s -> s.length() > 5);
// Consumer<T> — consomme sans retour
Consumer<String> print = System.out::println;
// Supplier<T> — produit une valeur
Supplier<List<String>> newList = ArrayList::new;
// BiFunction<T, U, R>
BiFunction<String, String, String> concat = (a, b) -> a + b;
4. Optional — Fin des NullPointerException
Optional<T> est un conteneur qui représente explicitement la présence ou l'absence d'une valeur, forçant le développeur à gérer les cas null.
// Création
Optional<String> present = Optional.of("valeur");
Optional<String> vide = Optional.empty();
Optional<String> nullable = Optional.ofNullable(getPossiblyNull());
// Utilisation en chaîne
String resultat = rechercherUtilisateur("alice")
.map(User::getNom)
.filter(n -> n.length() > 2)
.orElse("Inconnu");
// orElseGet — paresseux (lazy)
String nom = optional.orElseGet(() -> genererNomDefaut());
// orElseThrow
User user = optional.orElseThrow(() ->
new EntityNotFoundException("Utilisateur introuvable"));
// ifPresent
optional.ifPresent(v -> System.out.println("Trouvé : " + v));
5. API Date / Time — java.time
L'ancienne API (java.util.Date, Calendar) était notoire pour ses bugs et son manque d'intuitivité. Java 8 introduit java.time, inspirée de Joda-Time.
// Date seule (sans heure)
LocalDate today = LocalDate.now();
LocalDate noel = LocalDate.of(2025, Month.DECEMBER, 25);
long jours = ChronoUnit.DAYS.between(today, noel);
// Date + Heure
LocalDateTime maintenant = LocalDateTime.now();
LocalDateTime demain = maintenant.plusDays(1).withHour(9);
// Avec fuseau horaire
ZonedDateTime paris = ZonedDateTime.now(ZoneId.of("Europe/Paris"));
// Durée
Duration d = Duration.ofHours(2).plusMinutes(30);
// Formatage
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
String formatted = maintenant.format(fmt);
6. Méthodes default dans les interfaces
Java 8 permet d'ajouter des méthodes avec implémentation dans les interfaces via le mot-clé default. Cela permet d'enrichir les interfaces existantes sans casser la compatibilité.
public interface Service {
void execute(); // méthode abstraite
default void executeWithLog() {
System.out.println("Début : " + LocalDateTime.now());
execute();
System.out.println("Fin : " + LocalDateTime.now());
}
static Service noOp() {
return () -> {}; // lambda qui implémente execute()
}
}
7. Références de méthodes
Les références de méthodes (::) sont du sucre syntaxique pour les lambdas qui appellent une méthode existante.
// Méthode statique
Function<String, Integer> parse = Integer::parseInt;
// Méthode d'instance sur objet spécifique
String prefix = "Hello";
Predicate<String> starts = prefix::startsWith;
// Méthode d'instance sur type arbitraire
Function<String, String> upper = String::toUpperCase;
// Constructeur
Supplier<ArrayList> newList = ArrayList::new;
// Usage pratique
noms.stream()
.map(String::trim)
.filter(String::isBlank)
.forEach(System.out::println);
8. Récapitulatif Java 8
Lambdas
Fonctions anonymes concises, fin des classes anonymes verbeux.
Stream API
Traitement déclaratif des collections : filter, map, reduce, collect.
Optional<T>
Gestion explicite et sécurisée des valeurs potentiellement nulles.
java.time API
LocalDate, LocalDateTime, ZonedDateTime — gestion des dates modernisée.
Méthodes default
Implémentations par défaut dans les interfaces, rétrocompatibilité.
Références de méthodes
Classe::méthode — lambdas encore plus concis et lisibles.
Java 8 a atteint sa fin de support Oracle gratuit en janvier 2019, mais reste largement utilisé en entreprise. La plupart des frameworks (Spring Boot, Hibernate) ont migré vers Java 17+ comme minimum recommandé.