Avec la sortie de Java 16, les records ne sont plus en preview, il est donc tout à fait envisageable de les utiliser !
Prenons le temps d'un article pour éprouver un peu ce nouveau jouet.
Késako
Les records
viennent compléter la boîte à outils des "structures" Java (avec les classes
, les interfaces
, les enums
, ...). Pour un record
, comme au sirop, "C’qui compte, c’est les valeurs !" - Franck (Perceval) Pitiot.
Les records
sont définis par leurs valeurs, ils sont donc immuables (avec tous les bienfaits de ce pattern). Il a toujours été possible de coder des classes
immuables et définies par leurs valeurs mais il faut bien admettre que ce n'était pas gratuit. Maintenant, on peut faire :
public record Title(Level level, Label label) {
}
Nous donnera un Title
défini par un Level
et un Label
.
Comme un record
est défini par ses valeurs, on a automatiquement les methods equals
, hashCode
et toString
qui sont implémentées en se basant sur ces valeurs. Cela implique aussi qu'on ne peut pas avoir d'autres attributs que ceux définis dans la déclaration.
On a aussi des accesseurs en lecture aux deux attributs avec level()
et label()
et non pas getLevel()
et getLabel()
ce qui est peut-être la première pierre d'un changement de style dans Java.
Comme pour les classes
, on peut définir des records
privés (dans le même fichier que la structure qui en a besoin) et locaux :
public void buildSummary() {
record Title(Level level, Label label) {}
// Some stuff using title
}
Voilà pour le résumé très rapide, voyons maintenant ce que l’on peut faire avec !
Les constructeurs des records
Les records
ont des constructeurs qui sont générés en fonction de leurs attributs. Pour Title
, on a déjà ce constructeur dans le parent :
public record Title(Level level, Label label) {
public Title(Level level, Label label) {
this.level = level;
this.label = label;
}
}
Il peut être surchargé en respectant les règles classiques de surcharge (on ne pourra pas baisser la visibilité de cette signature, par exemple). Il faut bien garder en tête que les attributs d'un record
sont final
: on doit donc tous les affecter dans le constructeur, sans quoi, le code ne compilera pas.
Il est possible d'ajouter des traitements et de la logique dans un constructeur avec une syntaxe un peu allégée :
public record Title(Level level, Label label) {
public Title {
Assert.notNull("level", level);
Assert.notNull("label", label);
}
}
Dans ce cas, on a un call implicite au constructeur avec tous les paramètres.
Il est aussi possible de créer de nouveau constructeurs invoquant le constructeur par défaut :
public record Title(Level level, Label label) {
public Title() {
this(Level.ONE, new Label("Title"));
}
}
Cette dernière option ouvre la porte à l'utilisation de builders ou à tout autre pattern de construction.
Il n'est cependant pas possible de masquer le constructeur par défaut !
Les records avec un seul attribut
J'ai pour habitude de faire des "types" en Java en définissant des classes
avec un seul attribut et, parfois, une logique propre à cet attribut. Ces "types" seront maintenant bien plus simples à créer :
public record Label(String label) {
public Label(String label) {
Assert.notBlank("label", label);
this.label = label;
}
public String get() {
return label();
}
}
Dans ce cas, je pense que la définition d'une method get()
peut être une bonne idée pour éviter des invocations du type label.label()
(je trouve que label.get()
est plus élégant).
Dans certains cas, il peut être intéressant d'ajouter des static factory dans nos records
. Ici, on peut imaginer construire un Label
depuis un titre dans un markdown :
public record Label(String label) {
public Label(String label) {
Assert.notBlank("label", label);
this.label = label;
}
public static Label fromTitle(String title) {
Assert.notNull("title", title);
return new Label(title.replaceAll("^#+\\s*(.+)$", "$1"));
}
}
Les records
sont immuables. Modifier un record
c'est en créer un nouveau. Il peut donc être intéressant d'avoir ce type de methods :
public Label toUpperCase() {
return new Label(label().toUpperCase());
}
Je pense que je vais faire des quantités non négligeables de records
sur ce modèle dans mes projets tant je trouve l'approche élégante et pratique ! Cette capacité à créer des "types" beaucoup plus simplement va me faire gagner pas mal de temps.
Les records avec plusieurs attributs
Il est possible de définir des records
avec plusieurs attributs. Je pense qu'il faut quand même faire attention à ne pas tomber dans l'excès, car leur déclaration peut être rapidement illisible.
Comme pour les classes
, s’il y a moins de 3 attributs dans notre record
un constructeur prenant en paramètre ces attributs sera utilisable. Au-delà, il faudra passer par des patterns de construction plus riches qui évitent les erreurs et masquent cette complexité d'instanciation.
Dans tous les cas, pouvoir associer des attributs dans des objets naturellement immuables et définis par leurs valeurs va ajouter du confort au quotidien.
Un game changer ?
Je travaille majoritairement sur des applications mettant le Métier au centre dans des architectures hexagonales. Dans mon quotidien, les records
vont donc m'éviter des quantités non négligeables de code à faible apport Métier.
Ils ne vont cependant pas changer en profondeur nos applications puisqu'ils n'apportent rien qui n'était pas déjà faisable. Ils sont un très bel ajout syntaxique, un nouvel outil pratique et un pas vers le pattern matching !