Les records Java
Utilisez les records Java pour créer des classes immuables compactes avec accesseurs, equals et hashCode générés automatiquement.
Un record est une classe dont le seul rôle est de transporter des données. Vous déclarez les champs une seule fois dans l'en-tête, et le compilateur génère pour vous le constructeur, les accesseurs, equals, hashCode et toString. Ce qui nécessitait auparavant 40 lignes de getters et de code répétitif tient désormais en une seule ligne :
public record Point(int x, int y) {}C'est toute la classe. new Point(3, 4), p.x(), p.y(), p.equals(other) et p.toString() fonctionnent tous et se comportent exactement comme on s'y attendrait.
Les records ont été finalisés dans Java 16 (après des aperçus dans les versions 14 et 15). Cette page couvre ce que le compilateur génère, comment valider et personnaliser un record, les règles qu'il impose, et comment les records s'associent au pattern matching.
Pourquoi les records existent
Avant les records, chaque « classe de données » nécessitait un champ private final par composant, un constructeur les assignant, un getter par champ, des méthodes equals et hashCode basées sur la valeur, et un toString. La majeure partie de ce code était mécanique et facile à mal écrire subtilement (oublier un champ dans equals, retourner le mauvais champ d'un getter, copier-coller une faute de frappe). Les records condensent tout cela dans un en-tête, éliminant à la fois la saisie et les bugs.
Composants et accesseurs
Les arguments dans l'en-tête sont appelés composants. Chacun devient :
- Un champ
private finalportant le même nom. - Une méthode accesseur avec le même nom (pas
getX()— justex()).
Point p = new Point(3, 4);
System.out.println(p.x() + ", " + p.y()); // 3, 4Les noms correspondent parce que les records visent à exposer les données clairement. Il n'y a pas de préfixe get car il n'y a rien à cacher.
equals, hashCode et toString générés
Deux records sont égaux si et seulement s'ils sont du même type et que chaque composant est égal :
Point a = new Point(3, 4);
Point b = new Point(3, 4);
System.out.println(a.equals(b)); // true
System.out.println(a); // Point[x=3, y=4]hashCode combine tous les composants, de sorte que les records fonctionnent correctement comme clés dans HashMap et HashSet sans effort supplémentaire.
Constructeur compact
Chaque record possède un constructeur canonique — celui dont les paramètres correspondent aux composants dans l'ordre. Par défaut, le compilateur l'écrit pour vous (il assigne simplement chaque paramètre à son champ). Lorsque vous avez besoin de validation ou de normalisation, vous n'avez pas à répéter ces assignations : écrivez un constructeur compact, qui ne prend pas de liste de paramètres et s'exécute avant les assignations implicites des champs :
public record Range(int low, int high) {
public Range {
if (low > high)
throw new IllegalArgumentException("low > high");
}
}Vous pouvez également réassigner les variables de paramètre à l'intérieur du constructeur compact — les valeurs finales sont ce que les champs reçoivent :
public record Name(String first, String last) {
public Name {
first = first.strip();
last = last.strip();
}
}Ajout de méthodes
Les records peuvent avoir n'importe quelle méthode que vous écririez normalement — ils ne peuvent simplement pas avoir de champs d'instance supplémentaires (tout ce qui supporte le record doit être un composant) :
public record Point(int x, int y) {
public double distanceTo(Point other) {
int dx = x - other.x;
int dy = y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
}Les champs statiques et les méthodes statiques sont autorisés. Les records peuvent également implémenter des interfaces.
Ce que les records ne peuvent pas faire
- Pas d'héritage. Un record étend implicitement
java.lang.Recordet estfinal— vous ne pouvez pas étendre un record et un record ne peut pas étendre une autre classe. - Pas d'état mutable. Tous les composants sont
final. Si vous souhaitez de la mutation, utilisez une classe normale. - Pas de champs d'instance en dehors des composants. Vous ne pouvez pas glisser un
private int cache;supplémentaire.
Ces restrictions sont le but. Un record promet « je suis juste mes composants, rien d'autre. » C'est cette promesse qui rend equals, hashCode et la sérialisation sûrs à générer automatiquement.
Quand en utiliser un
Les records conviennent quand vous écririez sinon une petite classe de données immuable — DTOs, objets de configuration, types de retour regroupant quelques valeurs, tuples dans les expressions switch avec pattern matching. Ils ne conviennent pas quand le type a une identité, possède un état mutable ou est la racine d'une hiérarchie.
Un exemple concret
Les records en pattern matching
Comme les composants d'un record sont publics et ordonnés, le compilateur sait également comment décomposer un record. Un pattern de record lie chaque composant à une variable en une seule étape, ce qui fait des records la forme naturelle pour les données sur lesquelles on applique switch :
sealed interface Shape permits Circle, Rect {}
record Point(int x, int y) {}
record Circle(Point center, double r) implements Shape {}
record Rect(Point a, Point b) implements Shape {}
static String describe(Shape s) {
return switch (s) {
case Circle(Point c, double r) -> "circle r=" + r + " at " + c.x() + "," + c.y();
case Rect(Point a, Point b) -> "rect " + a + " to " + b;
};
}Le pattern Circle(Point c, double r) vérifie que s est un Circle et en extrait les composants en une seule expression. Voir le pattern matching Java pour la fonctionnalité complète, y compris les patterns imbriqués.
La suite
Les records verrouillent une classe à un ensemble fixe de données. Le prochain chapitre introduit les classes scellées, qui verrouillent une hiérarchie à un ensemble fixe de sous-types — la pièce manquante pour modéliser des familles de types de données algébriques fermées en Java. Continuez vers les classes scellées Java.