Java Records and the rest

https://stackoverflow.com/questions/1612334/difference-between-dto-vo-pojo-javabeans

https://stackoverflow.com/questions/62455515/how-can-i-use-java-records-as-dto-with-modelmapper

Les Records

La première grande fonctionnalité du langage fournie dans Java 16 s’appelle les Records. Les Records permettent de représenter des données sous forme de données dans du code Java plutôt que sous forme de classes arbitraires. Avant Java 16, lorsque nous avions simplement besoin de représenter certaines données, nous nous retrouvions avec une classe arbitraire comme celle indiquée dans l’extrait de code ci-dessous.

public class Product {
  private String name;
  private String vendor;
  private int price;
  private boolean inStock;
}

Ici, nous avons une classe Product qui a quatre membres. Cela devrait être toutes les informations dont nous avons besoin pour définir cette classe. Bien sûr, nous avons besoin de beaucoup plus de code pour que cela fonctionne. Par exemple, nous avons besoin d’un constructeur. Nous avons besoin de méthodes getter correspondantes pour obtenir les valeurs des membres. Pour le compléter, nous devons également avoir des implémentations d’equals(), hashCode() et toString() qui sont congruentes avec les membres que nous avons défini. Une partie de ce code boilerplate peut être généré par un IDE, mais cela présente certains inconvénients. Vous pouvez également utiliser des frameworks comme Lombok, mais ils présentent également certains inconvénients.

Ce dont nous avons vraiment besoin, c’est d’un mécanisme dans le langage Java pour décrire plus précisément ce concept d’avoir des classes de données uniquement. Et donc en Java 16, nous avons le concept de Records. Dans l’extrait de code suivant, nous avons redéfini la classe Product en tant que record.

public record Product(
    String name,
    String vendor,
    int price,
    boolean inStock) {
}

Notez l’introduction du nouveau mot clé record. Nous devons spécifier le nom du type record juste après le mot-clé record. Dans notre exemple, le nom est Product. Et puis nous n’avons qu’à fournir les composants qui composent ces Records. Ici, nous avons fourni les quatre composants en donnant leurs types et les noms. Et puis nous avons terminé. Un Record en Java est une forme spéciale d’une classe qui ne contient que ces données.

Que nous offre un record ? Une fois que nous avons une déclaration d’un record, nous obtenons une classe qui a un constructeur implicite acceptant toutes les valeurs des composants du Record. Nous obtenons automatiquement des implémentations pour les méthodes equals(), hashCode() et toString() basées sur tous les composants du record. De plus, nous obtenons également des méthodes d’accès pour chaque composant que nous avons dans les Records. Dans notre exemple ci-dessus, nous obtenons une méthode name, une méthode vendor, une méthode price et une méthode inStock qui renvoient respectivement les valeurs réelles des composants des records.

Les records sont toujours immuables. Il n’y a pas de méthodes de type setter. Une fois qu’un record est instancié avec certaines valeurs, vous ne pouvez plus le modifier. De plus, les classes records sont finales. Vous pouvez implémenter une interface avec un record, mais vous ne pouvez étendre d’aucune autre classe lors de la définition d’un record. Dans l’ensemble, il y a quelques restrictions ici. Mais les records nous offrent un moyen très puissant de définir de manière concise des classes de données uniquement dans nos applications.

Comment penser aux records

Comment devez-vous penser et aborder ces nouveaux éléments du langage ? Un record est une forme nouvelle et restreinte d’une classe utilisée pour modéliser des données en tant que données. Il n’est pas possible d’ajouter un état supplémentaire à un record, vous ne pouvez pas définir de champs (non statiques) en plus des composants d’un record. Les recordss concernent vraiment la modélisation de données immuables. Vous pouvez également considérer les records comme des tuples, mais pas seulement des tuples dans un sens générique que certains autres langages ont où vous avez des composants arbitraires qui peuvent être référencés par index. En Java, les éléments de tuple ont des noms réels, et le type de tuple lui-même, le record, a également un nom, car les noms ont de l’importance en Java.

Comment ne pas penser aux records

Il y a aussi certaines façons dont nous pouvons être tentés de penser aux records qui ne sont pas tout à fait appropriées. Tout d’abord, ils ne sont pas conçus comme un mécanisme de réduction de code boilerplate pour vos codes existants. Bien que nous ayons maintenant une manière très concise de définir ces records, cela ne signifie pas que des données telles que la classe de votre application peuvent être facilement remplacées par des records, principalement en raison des limitations imposées par les records. Ce n’est pas non plus vraiment le but de la conception.

L’objectif de conception des records est d’avoir un bon moyen de modéliser les données en tant que données. Ce n’est pas non plus un remplacement direct des JavaBeans, car comme je l’ai mentionné plus tôt, les méthodes d’accès, par exemple, ne respectent pas les normes des getter des JavaBeans. Et les JavaBeans sont généralement mutables, alors que les records sont immuables. Même s’ils servent un objectif quelque peu similaire, les records ne remplacent pas les JavaBeans de manière significative. Vous ne devez pas non plus considérer les records comme des types de valeur (Value types).

Les types de valeur peuvent être fournis en tant qu’amélioration du langage dans une future version de Java où les types de valeur concernent essentiellement la disposition de la mémoire et la représentation efficace des données dans les classes. Bien sûr, ces deux mondes peuvent se rencontrer à un moment donné, mais pour l’instant, les records ne sont qu’un moyen plus concis d’exprimer des classes de données uniquement.

En savoir plus sur les records

Considérons le code suivant où nous créons des records p1 et p2 de type Product avec exactement les mêmes valeurs.

Product p1 = new Product("peanut butter", "my-vendor", 20, true);
Product p2 = new Product("peanut butter", "my-vendor", 20, true);

On peut comparer ces records par égalité de références et on peut aussi les comparer à l’aide de la méthode equals(), celle qui a été fournie automatiquement par l’implémentation du record.

System.out.println(p1 == p2); // Prints false
System.out.println(p1.equals(p2)); // Prints true

Ce que vous verrez ici, c’est que ces deux records sont deux instances différentes, donc la comparaison de référence sera évaluée à false. Mais lorsque nous utilisons equals(), il ne regarde que les valeurs de ces deux records et il sera évalué à true. Parce qu’il ne s’agit que des données qui se trouvent à l’intérieur du record. Pour réitérer, les implémentations d’égalité et de hashcode sont entièrement basées sur les valeurs que nous fournissons au constructeur pour un record.

Une chose à noter est que vous pouvez toujours remplacer l’une des méthodes d’accès, ou les implémentations d’égalité et de hashcode, à l’intérieur d’une définition d’un record. Cependant, il vous appartiendra de préserver la sémantique de ces méthodes dans le cadre d’un record. Et vous pouvez ajouter des méthodes supplémentaires à une définition d’un record. Vous pouvez également accéder aux valeurs d’un record dans ces nouvelles méthodes.

Une autre fonction importante que vous voudrez peut-être utiliser dans un record est la validation. Par exemple, vous souhaitez uniquement créer un record si l’entrée fournie au constructeur du record est valide. La méthode traditionnelle de validation consisterait à définir un constructeur avec des arguments d’entrée qui sont validés avant d’affecter les arguments aux variables membres. Mais avec les records, nous pouvons utiliser un nouveau format, le constructeur dit compact. Dans ce format, nous pouvons laisser de côté les arguments du constructeur formel. Le constructeur aura implicitement accès aux valeurs des composants. Dans notre exemple Product, nous pouvons dire que si le prix est inférieur à zéro, nous lançons une nouvelle IllegalArgumentException.

public record Product(
    String name,
    String vendor,
    int price,
    boolean inStock) {
  public Product {
    if (price < 0) {
      throw new IllegalArgumentException();
    }
  }
}

Comme vous pouvez le voir dans l’extrait de code ci-dessus, si le prix est supérieur à zéro, nous n’avons pas à effectuer de tâches manuellement. Les affectations des paramètres (implicites) du constructeur aux champs du record sont ajoutées automatiquement par le compilateur lors de la compilation de ce record.

Nous pouvons même faire la normalisation si nous le voulons. Par exemple, au lieu de lever une exception si le prix est inférieur à zéro, nous pouvons définir le paramètre de prix, qui est implicitement disponible, sur une valeur par défaut.

public Product {
  if (price < 0) {
    price = 100;
  }
}

Encore une fois, les affectations aux membres réels du records, les champs finaux qui font partie de la définition du record, sont insérées automatiquement par le compilateur à la fin de ce constructeur compact. Dans l’ensemble, un moyen très polyvalent et très agréable de définir en Java des classes de données uniquement.

Vous pouvez également déclarer et définir des records localement dans les méthodes. Cela peut être très pratique si vous avez un état intermédiaire que vous voulez utiliser à l’intérieur de votre méthode. Par exemple, disons que nous voulons définir un produit à prix réduit. Nous pouvons définir un record qui prend un Product et un boolean qui indique si le produit est à prix réduit ou non.

public static void main(String... args) {
  Product p1 = new Product("peanut butter", "my-vendor", 100, true);

  record DiscountedProduct(Product product, boolean discounted) {}

  System.out.println(new DiscountedProduct(p1, true));
}

Comme vous pouvez le voir dans l’extrait de code ci-dessus, nous n’aurons pas à fournir un corps pour la nouvelle définition du record. Et nous pouvons instancier le DiscountedProduct avec p1 et true comme arguments. Si vous exécutez le code, vous verrez que cela se comporte exactement de la même manière que les records de premier niveau dans un fichier source. Les records en tant que construction locale peuvent être très utiles dans les situations où vous voulez regrouper certaines données dans une étape intermédiaire du pipeline d’un Stream.

Où utiliseriez-vous des records

Il y a des endroits évidents où les records peuvent être utilisés. L’un de ces endroits est lorsque nous voulons utiliser des objets de transfert de données (DTO). Les DTO sont par définition des objets qui n’ont pas besoin d’identité ou de comportement. Ils servent uniquement à transférer des données. Par exemple, à partir de la version 2.12, la bibliothèque Jackson prend en charge la sérialisation et la désérialisation des records en JSON et dans d’autres formats supportés.

Les records seront également très utiles lorsque vous souhaitez que les clés d’une map soient composées de plusieurs valeurs qui agissent comme une clé composite. L’utilisation des recordss dans ce scénario sera très utile puisque vous obtenez automatiquement le comportement correct pour les implémentations d’equals et de hashcode. Et puisque les records peuvent également être considérés comme des tuples nominaux, un tuple où chaque composant a un nom, vous pouvez facilement voir qu’il sera très pratique d’utiliser records pour retourner plusieurs valeurs d’une méthode à l’appelant.

D’un autre côté, je pense que les records ne seront pas beaucoup utilisés lorsqu’il s’agira de l’API Java Persistence. Si vous voulez utiliser des records pour représenter des entités, ce n’est pas vraiment possible parce que les entités sont fortement basées sur la convention JavaBeans. Et les entités ont généralement tendance à être mutables plutôt qu’immuables. Bien sûr, il pourrait y avoir quelques opportunités lorsque vous instanciez des objets en vue de lecture seule dans des requêtes où vous pourriez utiliser des records au lieu de classes ordinaires.

Tout compte fait, je pense que c’est un développement très excitant que nous ayons maintenant des records en Java. Je pense qu’ils verront une utilisation répandue.