Les records (Java 16)
Notions théoriques
Le problème des classes de données
En Java, créer une simple classe pour stocker des données demande beaucoup de code répétitif :
// Classe traditionnelle pour stocker un résultat de combat
public class ResultatCombat {
private final String vainqueur;
private final int toursJoues;
public ResultatCombat(String vainqueur, int toursJoues) {
this.vainqueur = vainqueur;
this.toursJoues = toursJoues;
}
public String getVainqueur() { return vainqueur; }
public int getToursJoues() { return toursJoues; }
@Override
public boolean equals(Object o) { /* ... 10 lignes ... */ }
@Override
public int hashCode() { /* ... */ }
@Override
public String toString() { return "ResultatCombat[vainqueur=" + vainqueur + ", toursJoues=" + toursJoues + "]"; }
}
Les records : la solution concise (Java 16)
Un record permet de déclarer tout cela en une seule ligne :
record ResultatCombat(String vainqueur, int toursJoues) {}
Java génère automatiquement :
- Le constructeur avec tous les paramètres
- Les accesseurs (getters) :
vainqueur()ettoursJoues()(sans "get") equals(),hashCode()ettoString()cohérents
Syntaxe d'un record
public record ResultatCombat(String vainqueur, int toursJoues) {}
Utilisation :
ResultatCombat resultat = new ResultatCombat("Thor", 5);
System.out.println(resultat.vainqueur()); // "Thor" (sans "get")
System.out.println(resultat.toursJoues()); // 5
System.out.println(resultat); // "ResultatCombat[vainqueur=Thor, toursJoues=5]"
Les accesseurs d'un record s'appellent sans "get" : resultat.vainqueur() et non resultat.getVainqueur(). C'est une différence importante avec les classes traditionnelles.
Les records sont immuables
Les champs d'un record sont final implicitement. Une fois créé, un record ne peut pas être modifié.
ResultatCombat r = new ResultatCombat("Thor", 5);
// r.vainqueur = "Elara"; // ERREUR : les champs d'un record sont final
C'est idéal pour les données de résultat : un résultat de combat ne doit pas changer après avoir été calculé.
Compact constructor pour validation
Un compact constructor permet de valider les données à la création, sans redéclarer les paramètres :
public record ResultatCombat(String vainqueur, int toursJoues) {
// Compact constructor : les paramètres sont implicitement disponibles
ResultatCombat {
if (vainqueur == null || vainqueur.isBlank()) {
throw new IllegalArgumentException("Le nom du vainqueur ne peut pas être vide.");
}
if (toursJoues <= 0) {
throw new IllegalArgumentException("Le nombre de tours doit être positif.");
}
}
}
Records et interfaces
Un record ne peut pas hériter d'une classe (il hérite implicitement de java.lang.Record), mais il peut implémenter des interfaces.
public interface Resumable {
String resumer();
}
public record ResultatCombat(String vainqueur, int toursJoues) implements Resumable {
@Override
public String resumer() {
return vainqueur + " a gagné en " + toursJoues + " tour(s).";
}
}
Cas d'usage des records
Les records sont idéaux pour :
- Résultats de calcul :
ResultatCombat,StatistiquesRonde - Transfert de données (DTO) : données entre couches de l'application
- Clés composées : combinaison de plusieurs valeurs utilisée comme clé
Les records ne remplacent pas les classes ordinaires. Une classe Personnage avec un état mutable (les PV changent) ne peut pas être un record. Utilisez les records uniquement pour des données immuables et portées.
Exemple pratique
// Fichier : ResultatCombat.java
public record ResultatCombat(String vainqueur, int toursJoues) {
// Compact constructor avec validation
ResultatCombat {
if (vainqueur == null || vainqueur.isBlank()) {
throw new IllegalArgumentException("Le nom du vainqueur ne peut pas être vide.");
}
if (toursJoues <= 0) {
throw new IllegalArgumentException("Le nombre de tours doit être positif.");
}
}
// Méthode personnalisée dans le record
public String resumer() {
return "Vainqueur : " + vainqueur + " après " + toursJoues + " tour(s).";
}
}
// Fichier : StatistiquesCombat.java
public record StatistiquesCombat(int degatsTotal, int soinsTotal, int toursJoues) {
public double moyenneDegatsParTour() {
return toursJoues > 0 ? (double) degatsTotal / toursJoues : 0;
}
}
// Fichier : Main.java
public class Main {
public static void main(String[] args) {
// Simuler un combat
Guerrier guerrier = new Guerrier("Thor", 120, 18, "épée runique");
Mage mage = new Mage("Nécromancien", 100, 15, 80);
int tours = 0;
int degatsTotal = 0;
while (guerrier.estVivant() && mage.estVivant() && tours < 10) {
guerrier.attaquer(mage);
if (mage.estVivant()) mage.attaquer(guerrier);
tours++;
degatsTotal += guerrier.getForce() * 2;
}
String vainqueur = guerrier.estVivant() ? guerrier.getNom() : mage.getNom();
// Créer des records immuables pour stocker les résultats
ResultatCombat resultat = new ResultatCombat(vainqueur, tours);
StatistiquesCombat stats = new StatistiquesCombat(degatsTotal, 0, tours);
// Afficher les résultats
System.out.println(resultat.resumer());
System.out.println("Vainqueur (via accesseur) : " + resultat.vainqueur());
System.out.println("Tours joués : " + resultat.toursJoues());
System.out.println("Dégâts moyens/tour : " + stats.moyenneDegatsParTour());
System.out.println(resultat); // toString() auto-généré
}
}
Notez que resultat.vainqueur() s'appelle sans "get". System.out.println(resultat) affiche automatiquement ResultatCombat[vainqueur=Thor, toursJoues=3] grâce au toString() généré.
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Vous allez créer le record ResultatCombat avec validation, puis l'utiliser pour capturer le résultat d'un combat entre deux personnages.
Étape 1 — Déclarer le record ResultatCombat
Créez ResultatCombat.java avec deux champs : vainqueur (String) et toursJoues (int).
En Java, une méthode ne peut retourner qu'une seule valeur. Quand vous avez besoin de retourner plusieurs valeurs liées (le vainqueur ET le nombre de tours), un record est la solution élégante. Évitez de retourner un tableau ou une Map dans ce cas.
Étape 2 — Ajouter un compact constructor avec validation
Ajoutez un compact constructor qui vérifie que vainqueur n'est pas null ou vide, et que toursJoues est positif.
La validation dans le compact constructor garantit qu'il est impossible de créer un ResultatCombat invalide. Tout code qui obtient un ResultatCombat peut faire confiance à ses données. C'est le principe de "fail fast" : détecter les erreurs le plus tôt possible.
Étape 3 — Utiliser le record dans un combat simulé
Dans Main, simulez un combat en boucle et créez un ResultatCombat à la fin.
Un résultat de combat ne doit jamais changer après avoir été calculé. Le record garantit cette immuabilité. Si vous avez besoin de modifier un résultat, créez un nouveau record avec les nouvelles valeurs plutôt que de modifier l'existant.