JavaRush /Blog Java /Random-FR /Pause café #128. Guide des enregistrements Java

Pause café #128. Guide des enregistrements Java

Publié dans le groupe Random-FR
Source : abhinavpandey.dev Dans ce didacticiel, nous aborderons les bases de l'utilisation des enregistrements en Java. Les enregistrements ont été introduits dans Java 14 comme moyen de supprimer le code passe-partout autour de la création d'objets Value tout en tirant parti des objets immuables. Pause café #128.  Guide des enregistrements Java - 1

1. Notions de base

Avant d’aborder les entrées elles-mêmes, examinons le problème qu’elles résolvent. Pour ce faire, nous devrons nous rappeler comment les objets de valeur étaient créés avant Java 14.

1.1. Objets de valeur

Les objets de valeur font partie intégrante des applications Java. Ils stockent les données qui doivent être transférées entre les couches d'application. Un objet valeur contient des champs, des constructeurs et des méthodes pour accéder à ces champs. Vous trouverez ci-dessous un exemple d'objet de valeur :
public class Contact {
    private final String name;
    private final String email;

    public Contact(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }
}

1.2. Égalité entre les objets de valeur

Les objets de valeur peuvent également fournir un moyen de comparer leur égalité. Par défaut, Java compare l'égalité des objets en comparant leur adresse mémoire. Cependant, dans certains cas, les objets contenant les mêmes données peuvent être considérés comme égaux. Pour implémenter cela, nous pouvons remplacer les méthodes equals et .hashCode . Implémentons-les pour la classe Contact :
public class Contact {

    // ...

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Contact contact = (Contact) o;
        return Object.equals(email, contact.email) &&
                Objects.equals(name, contact.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, email);
    }
}

1.3. Immuabilité des objets de valeur

Les objets de valeur doivent être immuables. Cela signifie que nous devons limiter les façons dont nous pouvons modifier les champs d'un objet. Ceci est conseillé pour les raisons suivantes :
  • Pour éviter le risque de modifier accidentellement la valeur du champ.
  • Faire en sorte que les objets égaux restent les mêmes tout au long de leur vie.
Puisque la classe Contact est déjà immuable, nous :
  1. rendu les champs privés et définitifs .
  2. fourni uniquement un getter pour chaque champ (pas de setters ).

1.4. Enregistrement d'objets de valeur

Nous devons souvent enregistrer les valeurs contenues dans les objets. Cela se fait en fournissant une méthode toString . Chaque fois qu'un objet est enregistré ou imprimé, la méthode toString est appelée . Le moyen le plus simple ici est d’imprimer la valeur de chaque champ. Voici un exemple :
public class Contact {
    // ...
    @Override
    public String toString() {
        return "Contact[" +
                "name='" + name + '\'' +
                ", email=" + email +
                ']';
    }
}

2. Réduisez les modèles avec les enregistrements

Étant donné que la plupart des objets de valeur ont les mêmes besoins et fonctionnalités, il serait bien de simplifier le processus de création de ceux-ci. Voyons comment les enregistrements contribuent à y parvenir.

2.1. Conversion de la classe Person en Record

Créons une entrée de classe Contact qui a les mêmes fonctionnalités que la classe Contact définie ci-dessus.
public record Contact(String name, String email) {}
Le mot - clé record est utilisé pour créer une classe Record . Les enregistrements peuvent être traités par l'appelant de la même manière qu'une classe. Par exemple, pour créer une nouvelle instance d'entrée, nous pouvons utiliser le nouveau mot-clé .
Contact contact = new Contact("John Doe", "johnrocks@gmail.com");

2.2. Comportement par défaut

Nous avons réduit le code à une seule ligne. Listons ce qu'il comprend :
  1. Les champs nom et email sont privés et définitifs par défaut.

  2. Le code définit un « constructeur canonique » qui prend des champs comme paramètres.

  3. Les champs sont accessibles via des méthodes de type getter - name() et email() . Il n'y a pas de paramètre pour les champs, donc les données de l'objet deviennent immuables.

  4. Implémentation de la méthode toString pour imprimer les champs comme nous l'avons fait pour la classe Contact .

  5. Implémentation des méthodes égales et .hashCode . Ils incluent tous les champs, tout comme la classe Contact .

2.3 Constructeur canonique

Le constructeur par défaut prend tous les champs comme paramètres d'entrée et les définit comme champs. Par exemple, le constructeur canonique par défaut est présenté ci-dessous :
public Contact(String name, String email) {
    this.name = name;
    this.email = email;
}
Si nous définissons un constructeur avec la même signature dans la classe d'enregistrement, il sera utilisé à la place du constructeur canonique.

3. Travailler avec des enregistrements

Nous pouvons modifier le comportement de l'entrée de plusieurs manières. Examinons quelques cas d'utilisation et comment les réaliser.

3.1. Remplacement des implémentations par défaut

Toute implémentation par défaut peut être modifiée en la remplaçant. Par exemple, si nous voulons modifier le comportement de la méthode toString , nous pouvons la remplacer entre les accolades {} .
public record Contact(String name, String email) {
    @Override
    public String toString() {
        return "Contact[" +
                "name is '" + name + '\'' +
                ", email is" + email +
                ']';
    }
}
De même, nous pouvons remplacer les méthodes equals et hashCode .

3.2. Kits de construction compacts

Parfois, nous souhaitons que les constructeurs fassent plus que simplement initialiser des champs. Pour ce faire, nous pouvons ajouter les opérations nécessaires à notre entrée dans le Constructeur Compact. Il est appelé compact car il n'a pas besoin de définir l'initialisation du champ ou la liste des paramètres.
public record Contact(String name, String email) {
    public Contact {
        if(!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
    }
}
Notez qu'il n'y a pas de liste de paramètres et que l'initialisation du nom et de l'adresse e-mail s'effectue en arrière-plan avant que la vérification ne soit effectuée.

3.3. Ajout de constructeurs

Vous pouvez ajouter plusieurs constructeurs à un enregistrement. Examinons quelques exemples et limitations. Tout d'abord, ajoutons de nouveaux constructeurs valides :
public record Contact(String name, String email) {
    public Contact(String email) {
        this("John Doe", email);
    }

    // replaces the default constructor
    public Contact(String name, String email) {
        this.name = name;
        this.email = email;
    }
}
Dans le premier cas, le constructeur par défaut est accessible à l'aide du mot-clé this . Le deuxième constructeur remplace le constructeur par défaut car il possède la même liste de paramètres. Dans ce cas, l'entrée elle-même ne créera pas de constructeur par défaut. Il existe plusieurs restrictions sur les constructeurs.

1. Le constructeur par défaut doit toujours être appelé depuis n’importe quel autre constructeur.

Par exemple, le code ci-dessous ne sera pas compilé :
public record Contact(String name, String email) {
    public Contact(String name) {
        this.name = "John Doe";
        this.email = null;
    }
}
Cette règle garantit que les champs sont toujours initialisés. Il est également garanti que les opérations définies dans le constructeur compact sont toujours exécutées.

2. Il n'est pas possible de remplacer le constructeur par défaut si un constructeur compact est défini.

Lorsqu'un constructeur compact est défini, un constructeur par défaut est automatiquement créé avec l'initialisation et la logique du constructeur compact. Dans ce cas, le compilateur ne nous permettra pas de définir un constructeur avec les mêmes arguments que le constructeur par défaut. Par exemple, dans ce code, la compilation n'aura pas lieu :
public record Contact(String name, String email) {
    public Contact {
        if(!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
    }
    public Contact(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

3.4. Implémentation d'interfaces

Comme pour n’importe quelle classe, nous pouvons implémenter des interfaces dans les enregistrements.
public record Contact(String name, String email) implements Comparable<Contact> {
    @Override
    public int compareTo(Contact o) {
        return name.compareTo(o.name);
    }
}
Note importante. Pour garantir une immuabilité totale, les enregistrements ne peuvent pas être hérités. Les inscriptions sont définitives et ne peuvent pas être étendues. Ils ne peuvent pas non plus étendre d’autres classes.

3.5. Ajout de méthodes

En plus des constructeurs, qui remplacent les méthodes et les implémentations d'interface, nous pouvons également ajouter toutes les méthodes de notre choix. Par exemple:
public record Contact(String name, String email) {
    String printName() {
        return "My name is:" + this.name;
    }
}
Nous pouvons également ajouter des méthodes statiques. Par exemple, si nous voulons avoir une méthode statique qui renvoie une expression régulière par rapport à laquelle nous pouvons vérifier le courrier électronique, nous pouvons la définir comme indiqué ci-dessous :
public record Contact(String name, String email) {
    static Pattern emailRegex() {
        return Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
    }
}

3.6. Ajout de champs

Nous ne pouvons pas ajouter de champs d'instance à un enregistrement. Cependant, nous pouvons ajouter des champs statiques.
public record Contact(String name, String email) {
    private static final Pattern EMAIL_REGEX_PATTERN = Pattern
            .compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);

    static Pattern emailRegex() {
        return EMAIL_REGEX_PATTERN;
    }
}
Notez qu'il n'y a aucune restriction implicite dans les champs statiques. Le cas échéant, ils peuvent être accessibles au public et non définitifs.

Conclusion

Les enregistrements sont un excellent moyen de définir des classes de données. Ils sont beaucoup plus pratiques et puissants que l’approche JavaBeans/POJO. Parce qu’ils sont faciles à mettre en œuvre, ils doivent être préférés aux autres moyens de créer des objets de valeur.
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION