Mot-clé d'enregistrement Java 14

1. Introduction

La transmission de données immuables entre des objets est l'une des tâches les plus courantes, mais banales, dans de nombreuses applications Java.

Avant Java 14, cela nécessitait la création d'une classe avec des champs et des méthodes standard, susceptibles de faire des erreurs triviales et des intentions confuses.

Avec la sortie de Java 14, nous pouvons désormais utiliser des enregistrements pour remédier à ces problèmes.

Dans ce didacticiel, nous examinerons les principes de base des enregistrements , y compris leur objectif, les méthodes générées et les techniques de personnalisation .

2. Objet

Généralement, nous écrivons des classes pour contenir simplement des données, telles que les résultats de la base de données, les résultats des requêtes ou les informations d'un service.

Dans de nombreux cas, ces données sont immuables, car l' immuabilité garantit la validité des données sans synchronisation .

Pour ce faire, nous créons des classes de données avec les éléments suivants:

  1. champ privé et final pour chaque donnée
  2. getter pour chaque champ
  3. constructeur public avec un argument correspondant pour chaque champ
  4. equals méthode qui renvoie true pour les objets de la même classe lorsque tous les champs correspondent
  5. méthode hashCode qui renvoie la même valeur lorsque tous les champs correspondent
  6. toString méthode qui inclut le nom de la classe et le nom de chaque champ et sa valeur correspondante

Par exemple, nous pouvons créer une simple classe de données Person , avec un nom et une adresse:

public class Person { private final String name; private final String address; public Person(String name, String address) { this.name = name; this.address = address; } @Override public int hashCode() { return Objects.hash(name, address); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (!(obj instanceof Person)) { return false; } else { Person other = (Person) obj; return Objects.equals(name, other.name) && Objects.equals(address, other.address); } } @Override public String toString() { return "Person [name=" + name + ", address=" + address + "]"; } // standard getters }

Bien que cela accomplisse notre objectif, il y a deux problèmes:

  1. Il y a beaucoup de code passe-partout
  2. Nous obscurcissons le but de notre classe - représenter une personne avec un nom et une adresse

Dans le premier cas, nous devons répéter le même processus fastidieux pour chaque classe de données, en créant de manière monotone un nouveau champ pour chaque élément de données, en créant des méthodes equals , hashCode et toString , et en créant un constructeur qui accepte chaque champ.

Bien que les IDE puissent générer automatiquement plusieurs de ces classes, ils ne parviennent pas à mettre à jour automatiquement nos classes lorsque nous ajoutons un nouveau champ . Par exemple, si nous ajoutons un nouveau champ, nous devons mettre à jour notre méthode equals pour incorporer ce champ.

Dans le second cas, le code supplémentaire obscurcit que notre classe est simplement une classe de données qui a deux champs String : nom et adresse .

Une meilleure approche serait de déclarer explicitement que notre classe est une classe de données.

3. Les bases

À partir du JDK 14, nous pouvons remplacer nos classes de données répétitives par des enregistrements. Les enregistrements sont des classes de données immuables qui ne nécessitent que le type et le nom des champs.

Les méthodes equals , hashCode et toString , ainsi que les champs privé , final et le constructeur public , sont générés par le compilateur Java.

Pour créer un enregistrement Personne , nous utilisons le mot clé d' enregistrement :

public record Person (String name, String address) {}

3.1. Constructeur

En utilisant des enregistrements, un constructeur public - avec un argument pour chaque champ - est généré pour nous.

Dans le cas de notre enregistrement Personne , le constructeur équivalent est:

public Person(String name, String address) { this.name = name; this.address = address; }

Ce constructeur peut être utilisé de la même manière qu'une classe pour instancier des objets à partir de l'enregistrement:

Person person = new Person("John Doe", "100 Linda Ln.");

3.2. Getters

Nous recevons également gratuitement des méthodes getters publiques - dont les noms correspondent au nom de notre champ.

Dans notre enregistrement Personne , cela signifie un getter de nom () et d' adresse () :

@Test public void givenValidNameAndAddress_whenGetNameAndAddress_thenExpectedValuesReturned() { String name = "John Doe"; String address = "100 Linda Ln."; Person person = new Person(name, address); assertEquals(name, person.name()); assertEquals(address, person.address()); }

3.3. équivaut à

De plus, une méthode equals est générée pour nous.

Cette méthode renvoie true si l'objet fourni est du même type et que les valeurs de tous ses champs correspondent:

@Test public void givenSameNameAndAddress_whenEquals_thenPersonsEqual() { String name = "John Doe"; String address = "100 Linda Ln."; Person person1 = new Person(name, address); Person person2 = new Person(name, address); assertTrue(person1.equals(person2)); }

Si l'un des champs diffère entre deux instances Person , la méthode equals renverra false .

3.4. hashCode

Semblable à notre méthode equals , une méthode hashCode correspondante est également générée pour nous.

Notre méthode hashCode retourne la même valeur pour deux objets Person si toutes les valeurs de champ pour les deux objets correspondent (sauf les collisions dues au paradoxe de l'anniversaire) :

@Test public void givenSameNameAndAddress_whenHashCode_thenPersonsEqual() { String name = "John Doe"; String address = "100 Linda Ln."; Person person1 = new Person(name, address); Person person2 = new Person(name, address); assertEquals(person1.hashCode(), person2.hashCode()); } 

La valeur hashCode sera différente si l'une des valeurs de champ diffère.

3.5. toString

Enfin, nous recevons également une méthode toString qui se traduit par une chaîne contenant le nom de l'enregistrement, suivi du nom de chaque champ et de sa valeur correspondante entre crochets .

Par conséquent, instancier une personne avec un nom de «John Doe» et une adresse de «100 Linda Ln. Donne le résultat toString suivant :

Person[name=John Doe, address=100 Linda Ln.]

4. Constructeurs

Bien qu'un constructeur public soit généré pour nous, nous pouvons toujours personnaliser notre implémentation de constructeur.

Cette personnalisation est destinée à être utilisée pour la validation et doit être aussi simple que possible.

Par exemple, nous pouvons nous assurer que le nom et l' adresse fournis à notre enregistrement Personne ne sont pas nuls à l'aide de l'implémentation de constructeur suivante:

public record Person(String name, String address) { public Person { Objects.requireNonNull(name); Objects.requireNonNull(address); } }

Nous pouvons également créer de nouveaux constructeurs avec différents arguments en fournissant une liste d'arguments différente:

public record Person(String name, String address) { public Person(String name) { this(name, "Unknown"); } }

Comme avec les constructeurs de classe, les champs peuvent être référencés à l'aide du mot - clé this (par exemple, this.name et this.address ) et les arguments correspondent au nom des champs (c'est-à-dire nom et adresse ).

Note that creating a constructor with the same arguments as the generated public constructor is valid, but this requires that each field be manually initialized:

public record Person(String name, String address) { public Person(String name, String address) { this.name = name; this.address = address; } }

Additionally, declaring a no-argument constructor and one with an argument list matching the generated constructor results in a compilation error.

Therefore, the following will not compile:

public record Person(String name, String address) { public Person { Objects.requireNonNull(name); Objects.requireNonNull(address); } public Person(String name, String address) { this.name = name; this.address = address; } }

5. Static Variables & Methods

As with regular Java classes, we can also include static variables and methods in our records.

We declare static variables using the same syntax as a class:

public record Person(String name, String address) { public static String UNKNOWN_ADDRESS = "Unknown"; }

Likewise, we declare static methods using the same syntax as a class:

public record Person(String name, String address) { public static Person unnamed(String address) { return new Person("Unnamed", address); } }

We can then reference both static variables and static methods using the name of the record:

Person.UNKNOWN_ADDRESS Person.unnamed("100 Linda Ln.");

6. Conclusion

Dans cet article, nous avons examiné le mot-clé record introduit dans Java 14, y compris leurs concepts fondamentaux et leurs subtilités.

En utilisant des enregistrements - avec leurs méthodes générées par le compilateur - nous pouvons réduire le code standard et améliorer la fiabilité de nos classes immuables.

Le code et les exemples de ce didacticiel se trouvent à l'adresse over sur GitHub.