2026-10-05
//echo: false
record Person(String email, String prenom, String nom, int age) {}
//Immutable List of Records
List<Person> personnes = List.of(
new Person("pierre.durand@a.fr","Pierre", "Durand", 20),
new Person("marie.durand@b.fr","Marie", "Durand", 14),
new Person("albert.martin@c.fr","Albert", "Martin", 30)
// ...
);
personnes;[Person[email=pierre.durand@a.fr, prenom=Pierre, nom=Durand, age=20], Person[email=marie.durand@b.fr, prenom=Marie, nom=Durand, age=14], Person[email=albert.martin@c.fr, prenom=Albert, nom=Martin, age=30]]
SLF4J(I): Connected with provider of type [ch.qos.logback.classic.spi.LogbackServiceProvider]
Cycle de Release (Java 9+ 2017)
| Version | Date Sortie | Fin Support | Caractéristiques |
|---|---|---|---|
| Java 8 | Mars 2014 | 2030 | Streams, Lambda |
| Java 11 | Sept 2018 | 2026 | HTTP Client, var |
| Java 17 | Sept 2021 | 2029 | Sealed, Records |
| Java 21 | Sept 2023 | 2031 | Virtual Threads |
--enable-previewDepuis Java 13 (standard en Java 15), les text blocks permettent de définir des chaînes multilignes plus lisibles:
"""{
"fistname" : "Pierre",
"lastname" : "Durand",
"age" : 12,
"description" : "lorem ipsum.................................................lorem ipsum"
}
// Méthodes pour accès séquentiel
SequencedCollection<String> list = new ArrayList<>();
list.addFirst("first");
list.addLast("last");
String first = list.getFirst();
String last = list.getLast();
// Maps séquentielles
SequencedMap<String, Integer> map = new LinkedHashMap<>();
map.putFirst("one", 1);
map.putLast("two", 2);Les record (standard depuis Java 16, preview en Java 14/15) sont une nouvelle forme concise de classe pour représenter des données immutables:
Classes immutables par défaut (tous les champs sont final)
Génération automatique de:
Syntaxe compacte
Dog[id=1, name=Rex]-> nom:Rex
Record implante automatiquement equals() et hashCode() basés sur tous leurs composants. Choix idéal pour:
// Crée un Stream à partir de 3 éléments (d1, d2, d3)
// Collecte les éléments dans un HashSet (élimine les doublons)
// toString() convertit le HashSet en représentation String pour l'affichage
System.out.println(Stream.of(d1,d2,d3) // Création du Stream
.collect(Collectors.toCollection(HashSet::new)) // Collecte dans HashSet
.toString() // Conversion en String
); [null]
Les records permettent certaines personnalisations tout en gardant leur nature immutable:
Possibilités de personnalisation:
Limitations importantes:
//| error: true
//| warning: true
record Person(String name, int age) {
// Constructeur avec validation
public Person {
if (age < 0) throw new IllegalArgumentException("Age must be positive");
if (name == null) throw new IllegalArgumentException("Name required");
}
// Méthode statique
public static Person of(String name, int age) {
return new Person(name, age);
}
// Méthode d'instance additionnelle
public boolean isAdult() {
return age >= 18;
}
}
Person.of("Pierre", -1);Execution exception
---------------------------------------------------------------------------
java.lang.IllegalArgumentException: Age must be positive
at Person.<init>(#57:6)
at Person.of(#57:12)
at .(#83:1)
Depuis Java 16 (preview en Java 12-15), l’opérateur instanceof s’enrichit du pattern matching:
Pierre
Le switch s’enrichit de nouvelles fonctionnalités:
public enum Day { SUNDAY, MONDAY, TUESDAY,
WEDNESDAY, THURSDAY, FRIDAY, SATURDAY; }
Day day = Day.WEDNESDAY;
//Day day = Day.TUESDAY;
int l =
switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
default -> throw new IllegalStateException("Invalid day: " + day);
};
l;9
record Dog(int id, String name) {};
record User(int id, String firstname) {};
Dog d1 = new Dog(1, "Rex");
User u1= new User(1,"Pierre");
public String check(Object obj) {
return switch (obj) {
case User u -> "I'm a user named "+u.firstname();
case Dog d -> "I'm a dog named "+d.name();
case null -> "NULL !";
default -> "A simple object: "+obj;
};
}
System.out.println("-->"+check(d1));
System.out.println("-->"+check(u1));
System.out.println("-->"+check(null));
System.out.println("-->"+check(new Integer(12)));-->I'm a dog named Rex
-->I'm a user named Pierre
-->NULL !
-->A simple object: 12
Record Pattern (preview Java 19+, standard Java 21):
10,20
Fonctionne aussi avec le switch :
record Person(String name, int age) {}
record Dog(String name, String breed) {}
Function<Object, String> generateStringRepresentation = (obj) -> switch(obj) {
case Person(String name, int age) ->
"Person: " + name + ", age " + age;
case Dog(String name, String breed) ->
"Dog: " + name + " is a " + breed;
default -> "Unknown";
};
Stream.of(new Person("Alice", 30), new Dog("Rex", "Labrador"), "test")
.map(generateStringRepresentation)
.forEach(System.out::println);Person: Alice, age 30
Dog: Rex is a Labrador
Unknown
Sealed Classes (depuis Java 17):
sealedpermits pour lister les classes autoriséesfinal, sealed ou non-sealedJava 8 introduit deux évolutions majeures pour les interfaces:
Méthodes par défaut (default methods):
defaultRésolution des conflits:
/**
* Interface générique définissant un contrat pour exécuter une action sur un type T.
* Introduit des fonctionnalités Java 8+ (méthodes default) et Java 9+ (méthodes statiques).
* @param <T> le type des éléments sur lesquels l'action sera exécutée
*/
public interface ActionClass<T> {
// Méthode statique introduite en Java 9
// Fournit une description de l'interface
static String getInterfaceDescription() {
return "Une interface qui impose de définir une action et permet de l'exécuter une liste de <T>";
}
/**
* Méthode abstraite définissant l'action à exécuter sur un élément
* @param t l'élément sur lequel exécuter l'action
*/
void action(T t);
/**
* Méthode default (Java 8+) fournissant une implémentation par défaut
* pour exécuter l'action sur une liste d'éléments
* Utilise la méthode action(T) pour chaque élément
* @param list la liste d'éléments à traiter
*/
default void action(List<T> list) {
for(T t:list) action(t);
}
}//Une classe qui affiche une String en majuscule et s'applique automatiquement à une liste de String
public class UpperCaser implements ActionClass<String> {
public void action(String s) {System.out.println(s.toUpperCase());}
}
//Une classe qui affiche la concaténation du nom et du prénom d'une personne et s'applique automatiquement à une liste de Personne
public class PersonFullname implements ActionClass<Person> {
public void action(Person p) {System.out.println(p.prenom()+" "+p.nom());}
}ABC
DEF
new PersonFullname().action(personnes);
cannot find symbol
symbol: variable personnes
Java 9 introduit le système de modules (Project Jigsaw) qui apporte une nouvelle unité de structuration du code.
Un module:
Avantages principaux:
Utilisation:
Plus de détails : https://openjdk.java.net/projects/jigsaw/quick-start
Depuis Java 10, le mot-clé var permet l’inférence de type pour les variables locales avec initialisation.
Fonctionnement:
{1=A, 2=B}
Limitations:
L’inférence de type avec var s’étend aux paramètres des expressions lambda:
var permet d’ajouter des annotationsvar si un l’utiliseNote
L’API Process modernise la gestion des processus système avec des fonctionnalités avancées et une API fluide.
ProcessHandle pour accès unifié
// 1. Information basique des processus
ProcessHandle current = ProcessHandle.current();
System.out.printf("Process ID: %d%n", current.pid());
System.out.printf("Parent PID: %d%n",
current.parent().map(ProcessHandle::pid).orElse(-1L));
// 2. Surveillance des processus enfants
current.children().forEach(child -> {
System.out.printf("Child process: %d%n", child.pid());
child.onExit().thenAccept(ph ->
System.out.printf("Child %d terminated%n", ph.pid()));
});Process ID: 11787
Parent PID: 408
// 2. Monitoring système
ProcessHandle.allProcesses()
.filter(ph -> ph.info().totalCpuDuration().isPresent())
.sorted(Comparator.comparing(ph ->
ph.info().totalCpuDuration().get()))
.limit(5)
.forEach(ph -> {
ProcessHandle.Info info = ph.info();
System.out.printf("PID: %d%n", ph.pid());
System.out.printf("Command: %s%n",
info.command().orElse("?"));
System.out.printf("CPU time: %s%n",
info.totalCpuDuration().get());
System.out.println("---");
});PID: 63
Command: ?
CPU time: PT0S
---
PID: 68
Command: ?
CPU time: PT0S
---
PID: 132
Command: ?
CPU time: PT0S
---
PID: 223
Command: /usr/bin/dash
CPU time: PT0S
---
PID: 6
Command: ?
CPU time: PT0.01S
---
// 3. Gestion avancée des processus
ProcessBuilder pb = new ProcessBuilder("ls", "-l")
.redirectOutput(ProcessBuilder.Redirect.PIPE);
Process process = pb.start();
ProcessHandle handle = process.toHandle();
// Gestion asynchrone
CompletableFuture<ProcessHandle> onExit = handle.onExit();
onExit.thenAccept(ph -> {
System.out.println("Process completed: " + ph.pid());
System.out.println("Exit code: " + process.exitValue());
});Process completed: 15270
Exit code: 0
java.util.concurrent.CompletableFuture@5a23f039[Completed normally]
Java 8 introduit Optional<T> pour gérer explicitement les valeurs potentiellement nulles. Cette classe conteneur offre une alternative plus sûre et fonctionnelle aux vérifications traditionnelles de null.
Création d’Optional
Optional.of(value) : crée un Optional avec une valeur non-null (lance NullPointerException si null)Optional.ofNullable(value) : crée un Optional qui peut contenir nullOptional.empty() : crée un Optional videMéthodes principales
orElse(defaultValue) : retourne la valeur ou une valeur par défautorElseThrow() : retourne la valeur ou lance NoSuchElementException (Java 10+)orElseThrow(Supplier) : permet de spécifier l’exceptionmap(), flatMap() : pour transformer la valeur si présentefilter() : pour filtrer selon une conditionBonnes pratiques
Optional.get() (déprécié conceptuellement)orElse() ou orElseThrow()value1
Execution exception
---------------------------------------------------------------------------
java.lang.NullPointerException: null
at java.base/java.util.Objects.requireNonNull(Objects.java:220)
at java.base/java.util.Optional.of(Optional.java:113)
at .(#113:1)
Il est possible de manipuler des références éventuellement nulles avec la factory ofNullable().
value2
Execution exception
---------------------------------------------------------------------------
java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.orElseThrow(Optional.java:377)
at .(#121:1)
Dans ce cas OrElse() est une alternative à pour obtenir une valeur par défault en cas de valeur nulle au lieu d’une Exception.
defaultValue3
OrElseGet(Supplier s) est une variante plus efficace si le calcul de la valeur par défaut est complexe car alors la fonction n’est évaluée qu’en cas de valeur nulle.
Dans l’exemple ci dessous en cas de valeur nulle, une entrée alétoire du tableau est retournée.
a1
Trois GC majeurs optimisés pour différents besoins
ZGC (Java 11+0 )
-XX:+UseZGCShenandoah (Java 12+)
-XX:+UseShenandoahGCG1 (défaut Java 9+)
-XX:+UseG1GCLatence critique (ZGC), usage général (Shenandoah), ou équilibré (G1)
Améliorations majeures du compilateur JIT
AOT Compilation
jaotc --output lib.so MyClass.classGraal JIT Compiler
-XX:+UseJVMCICompilerProfiling Optimisé
AOT pour démarrage rapide, Graal pour performance maximale
// Allocation mémoire native
try (Arena arena = Arena.ofConfined()) {
MemorySegment segment = arena.allocate(100);
// Écriture dans la mémoire
segment.set(ValueLayout.JAVA_LONG, 0, 42L);
segment.set(ValueLayout.JAVA_INT, 8, 17);
// Lecture de la mémoire
long value = segment.get(ValueLayout.JAVA_LONG, 0);
int number = segment.get(ValueLayout.JAVA_INT, 8);
System.out.println("Long value: " + value);
System.out.println("Int value: " + number);
} // Libération automatique de la mémoireLong value: 42
Int value: 17
// Import des classes pour l'API Foreign Function & Memory (FFM)
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
// 1. Configuration de l'appel à la fonction C 'strlen'
// Définition du prototype de la fonction: size_t strlen(const char*)
FunctionDescriptor strlenDesc = FunctionDescriptor.of(
ValueLayout.JAVA_LONG, // Type de retour: long (size_t en C)
ValueLayout.ADDRESS // Type du paramètre: pointeur vers char*
);
try {
// 2. Création du linker pour interagir avec le code natif
Linker linker = Linker.nativeLinker();
// 3. Recherche du symbole 'strlen' dans la bibliothèque standard C
SymbolLookup stdlib = linker.defaultLookup();
var strlenSymbol = stdlib.find("strlen")
.orElseThrow(() -> new RuntimeException("strlen non trouvée"));
// 4. Création du handle pour appeler strlen
MethodHandle strlen = linker.downcallHandle(strlenSymbol, strlenDesc);
// 5. Utilisation avec gestion automatique de la mémoire
try (Arena arena = Arena.ofConfined()) { // Arena libère la mémoire automatiquement
// Allocation et conversion de la chaîne en C-string
MemorySegment cString = arena.allocateFrom("Hello, World!");
// Appel de strlen
long length = (long) strlen.invoke(cString);
System.out.println("Longueur: " + length); // Affiche: 13
// Test avec une chaîne différente
MemorySegment other = arena.allocateFrom("Xin chào thế giới!");
System.out.println("Longueur UTF-8: " + (long)strlen.invoke(other));
}
} catch (Throwable e) {
System.err.println("Erreur lors de l'appel natif: " + e.getMessage());
e.printStackTrace();
}Longueur: 13
Longueur UTF-8: 23
Hidden Classes (depuis Java 15):
ByteBuddy facilite la création de ces classes:
{}
exporting a package from system module jdk.compiler is not allowed with --release
//| echo: false
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatchers;try {
// 1. Génération de la classe dynamique avec ByteBuddy
byte[] classBytes = new ByteBuddy()
.subclass(Object.class)
.name("REPL.DynamicHiddenClass")
// Définition d'une méthode publique retournant un String
.defineMethod("getMessage", String.class, Modifier.PUBLIC)
.intercept(FixedValue.value("Hello World ByteBuddy!"))
.make()
.getBytes();
// 2. Création de la hidden class
Lookup lookup = MethodHandles.lookup();
Class<?> hiddenClass = lookup.defineHiddenClass(
classBytes,
true, // initialisation immédiate
MethodHandles.Lookup.ClassOption.NESTMATE
).lookupClass();
// 4. Test de la classe cachée
Object instance = hiddenClass.getDeclaredConstructor().newInstance();
Method getMessage = hiddenClass.getDeclaredMethod("getMessage");
System.out.println("Message: " + getMessage.invoke(instance));
// 5. Vérification des propriétés de la hidden class
System.out.println("\nHidden Class Properties:");
System.out.println(" Class name: " + hiddenClass.getName());
System.out.println(" Is hidden: " + hiddenClass.isHidden());
System.out.println(" Package: " + hiddenClass.getPackageName());
System.out.println("\nCan access via reflection: (should generate an exception)");
Class.forName(hiddenClass.getName());
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
e.printStackTrace();
}Message: Hello World ByteBuddy!
Hidden Class Properties:
Class name: REPL.DynamicHiddenClass/0x00003fe001481800
Is hidden: true
Package: REPL
Can access via reflection: (should generate an exception)
Error: REPL.DynamicHiddenClass/0x00003fe001481800
java.lang.ClassNotFoundException: REPL.DynamicHiddenClass/0x00003fe001481800
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:462)
at java.base/java.lang.Class.forName(Class.java:453)
at REPL.$JShell$160.do_it$($JShell$160.java:101)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at io.github.spencerpark.ijava.execution.IJavaExecutionControl.lambda$execute$1(IJavaExecutionControl.java:95)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1575)
// Définition
private final ScopedValue<String> USER_ID = ScopedValue.newInstance();
private final ScopedValue<String> ROLE = ScopedValue.newInstance();
private static void processTask() {
// Les valeurs sont accessibles dans les méthodes appelées
System.out.println("Processing for: " + USER_ID.get() +
" with role: " + ROLE.get());
}
// Utilisation avec Virtual Threads
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
// Premier contexte
executor.submit(() ->
ScopedValue.where(USER_ID, "user1")
.where(ROLE, "ADMIN")
.run(() -> {
System.out.println("Thread 1 - User: " + USER_ID.get());
System.out.println("Thread 1 - Role: " + ROLE.get());
// Sous-tâche hérite des valeurs
processTask();
})
);
// Deuxième contexte (différentes valeurs)
executor.submit(() ->
ScopedValue.where(USER_ID, "user2")
.where(ROLE, "USER")
.run(() -> {
System.out.println("Thread 2 - User: " + USER_ID.get());
System.out.println("Thread 2 - Role: " + ROLE.get());
processTask();
})
);
}
Thread 1 - User: user1
Thread 1 - Role: ADMIN
Thread 2 - User: user2
Thread 2 - Role: USER
Processing for: user2 with role: USER
Processing for: user1 with role: ADMIN
public static Person fetchUser() {
// Simulate fetching user
return Person.of("John Doe",45);
}
public static String fetchMessage() {
// Simulate fetching message
return "Hello, World!";
}
ExecutorService executor = Executors.newCachedThreadPool();
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
StructuredTaskScope.Subtask<Person> user = scope.fork(() -> fetchUser());
StructuredTaskScope.Subtask<String> order = scope.fork(() -> fetchMessage());
scope.join(); // Attend tous les résultats
// Utilisation des résultats
System.out.println("User: " + user.get());
System.out.println("Order: " + order.get());
} finally {
executor.shutdown();
}
User: Person[name=John Doe, age=45]
Order: Hello, World!
class Point {
private final int x;
private final int y;
Point(int x, int y) {
// Bloc de validation avant initialisation
//AVANT L'APPEL DU SUPER CONSTRUCTEUR
try {
validateCoordinates(x, y);
} catch (Exception e) {
throw new IllegalArgumentException("Invalid coordinates", e);
}
super();
// Initialisation des champs
this.x = x;
this.y = y;
}
static void validateCoordinates(int x, int y) {
if (x < 0 || y < 0) throw new IllegalArgumentException();
}
}Optimisations vectorielles SIMD:
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorSpecies;
final VectorSpecies<Float> SPECIES_256 = FloatVector.SPECIES_256;
float[] array1 = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f};
float[] array2 = {8.0f, 7.0f, 6.0f, 5.0f, 4.0f, 3.0f, 2.0f, 1.0f};
var v1 = FloatVector.fromArray(SPECIES_256, array1, 0);
var v2 = FloatVector.fromArray(SPECIES_256, array2, 0);
var v3 = v1.mul(v2); // Multiplication vectorielle
System.out.println(v3);Execution exception
---------------------------------------------------------------------------
java.lang.NoClassDefFoundError: jdk/incubator/vector/FloatVector
at .(#178:1)
Large number: 123
E. Bruno - Noueautés à partir de Java 8