Participer au site avec un Tip
Rechercher
 

Améliorations / Corrections

Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.

Emplacement :

Description des améliorations :

Définition de classes de type « record » (Java SE 16)

Introduction à la POO et principe d'encapsulation Mise en oeuvre du concept d'héritage


Accès rapide :
Introduction aux notions de classes immuables et de records
Mise en oeuvre d'une classe immuable avec une syntaxe traditionnelle
Mise en oeuvre d'une classe immuable via la syntaxe « record »
Ajouter des membres dans un record
Ajouter des constructeurs
Ajouter des méthodes

Introduction aux notions de classes immuables et de records

Parfois, on peut juger utile de définir des classes immuables (ou immutables). Une telle classe permet de produire des objets avec un état initial mais qui ne pourront plus changer d'état une fois instanciés.

cela permet, notamment, de garantir l'aspect « thread-safe » pour ce type de classe, étant donné qu'il n'y aura jamais d'accès concurrent. Pour ceux qui ne connaissent pas encore le concept de threads, nous reviendrons sur ce sujet ultérieurement.

Vous connaissez déjà au moins une classe de ce type. Effectivement, la classe java.lang.String permet de produire des instances immuables. Une fois une chaîne instanciée, il n'est plus possible d'en modifier son état et ce jusqu'à sa libération par le Garbage Collector. De même la classe java.time.LocalDate, qui permet de gérer une date, produit aussi des instances immuables. Voici extrait de la JavaDoc officielle - « LocalDate is an immutable date-time object that represents a date, often viewed as year-month-day. ».

Pour produire de tels objets, nous n'avions, avant Java SE 16, qu'une seule et unique manière de procéder. Il fallait produire une classe réalisant une encapsulation de ses membres (attributs privés) et n'exposant que les getters et surtout pas les setters. Un certain volume de code était donc nécessaire pour produire une classe d'instances immuables.

Mais l'arrivée de Java SE 16, change la donne avec l'apparition du concept de « record ». Dans les cas les plus simples, une unique ligne de code peut permettre la définition d'une classe d'objets immuables. Nous allons donc pouvoir faire de grosses économies de lignes de codes.

en réalité le concept de record était déjà présent dans quelques versions antérieures à Java SE 16, mais il faillait activer le support « Preview Features » au démarrage de la JVM pour y avoir accès.
javac --enable-preview MyApp.java

Afin de comparer les choses, nous allons appréhender les deux approches, en commençant par l'approche historique.

Mise en oeuvre d'une classe immuable avec une syntaxe traditionnelle

Exemple à considérer une classe Point

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
package fr.koor.samples;

import java.util.Objects;

public class Point {

    /*
     *  Deux attributs finaux ne pouvant plus être
     *  modifiés après la construction de l'instance. 
     */
    private final double x;
    private final double y;
    
    /*
     * Un constructeur de classe avec une délégation 
     * à un second constructeur acceptant deux flottants.
     */
    public Point() {
        this( 0, 0 );
    }
    
    /*
     * Un second constructeur permettant de fixer l'état
     * initial des deux attributs de la classe.
     */
    public Point( double x, double y ) {
        this.x = x;
        this.y = y;
    }
    
    /*
     * Deux getters pour l'accès à l'état de vos points.
     * Les setters ne sont pas disponibles afin de garantir
     * l'aspect immuable (readonly) de vos objets.
     */
    public double getX() {
        return x;
    }
    
    public double getY() {
        return y;
    }

    /*
     * Cette méthode est utile si vous souhaitez utiliser des instances 
     * de points dans une table associative (type Hashtable ou HashMap).
     * Elle permettra de calculer le "hashcode" (en gros, la position 
     * dans la collection) d'un point donné.
     */
    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
    
    /*
     * Cette méthode permettra de comparer deux instances Java et
     * d'indiquer si elles sont identiques ou non.
     */
    @Override
    public boolean equals( Object object ) {
        if ( this == object ) {
            return true;
        } else if ( object instanceof Point ) {
            Point other = (Point) object;
            return x == other.x && y == other.y;
        } else {
            return false;
        }
    }
    
    /*
     * Pour calculer la chaîne de caractères de représentative d'un point.
     */
    @Override public String toString() {
        return "Point [x=" + x + ", y=" + y + "]";
    }
    
    // TODO: imaginez d'autres méthodes relatives à la gestion de vos points.
}
Mise en oeuvre d'une classe immuable avec une syntaxe Java traditionnelle
il est possible d'être assisté par Eclipse pour la production d'une grande partie du code présenté ci-dessus. Pour ce faire, vous pouvez cliquer avec le bouton droit de la souris sur votre classe et demander le menu contextuel « Source ». Vous y trouverez plusieurs assistants pour produire une bonne partie de ces codes, comme le montre la capture d'écran ci-dessous.
Divers outils pour générer le contenu d'une classe

Mise en oeuvre d'une classe immuable via la syntaxe « record »

Depuis la version 16 du Java SE, le concept de « record » est donc officiellement utilisable : ce concept permet de définir quasiment l'équivalent de la classe présentée ci-dessus en une seule ligne de code, comme le montre l'exemple suivant.

 1 
public record Point (double x, double y) {}

Il faut comprendre que cette simple définition a produit automatiquement un certain nombre d'éléments dans la classe. Ont automatiquement été définis :

Pour preuve, voici les résultats produits par l'outil javap invoqué sur le fichier fr/koo/samples/Point.class qui a été généré par le compilateur.

$> javap fr.koor.samples.Point
Compiled from "Point.java"
public final class fr.koor.samples.Point extends java.lang.Record {
  public fr.koor.samples.Point(double, double);
  public double x();
  public double y();
  public final java.lang.String toString();
  public final int hashCode();
  public final boolean equals(java.lang.Object);
}
$> javap -c fr.koor.samples.Point

Compiled from "Point.java"
public final class fr.koor.samples.Point extends java.lang.Record {
  public fr.koor.samples.Point(double, double);
    Code:
       0: aload_0
       1: invokespecial #11                 // Method java/lang/Record."<init>":()V
       4: aload_0
       5: dload_1
       6: putfield      #14                 // Field x:D
       9: aload_0
      10: dload_3
      11: putfield      #16                 // Field y:D
      14: return

  public double x();
    Code:
       0: aload_0
       1: getfield      #14                 // Field x:D
       4: dreturn

  public double y();
    Code:
       0: aload_0
       1: getfield      #16                 // Field y:D
       4: dreturn

  public final java.lang.String toString();
    Code:
       0: aload_0
       1: invokedynamic #26,  0             // InvokeDynamic #0:toString:(Lfr/koor/samples/Point;)Ljava/lang/String;
       6: areturn

  public final int hashCode();
    Code:
       0: aload_0
       1: invokedynamic #31,  0             // InvokeDynamic #0:hashCode:(Lfr/koor/samples/Point;)I
       6: ireturn

  public final boolean equals(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: invokedynamic #36,  0             // InvokeDynamic #0:equals:(Lfr/koor/samples/Point;Ljava/lang/Object;)Z
       7: ireturn
}
$> 
javap est un outil proposé de base par le JDK. Il permet d'analyser le contenu d'un fichier d'extension .class. L'option -c permet de demander un désassemblage. Nous sommes bien d'accord sur le fait que nous ne sommes pas là pour apprendre le langage machine d'un processeur Java : seules les noms des méthodes produites nous intéresse.

Vous pouvez donc maintenant utiliser votre type « record » quasiment comme s'il s'agissait d'une classe traditionnelle. Vous noterez néanmoins l'absence du préfixe get sur les méthodes d'accès aux attributs.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
package fr.koor.samples;

public class Start {

    public static void main( String[] args ) {
        
        Point p1 = new Point( 0, 0 );
        System.out.println( p1 );
        
        Point p2 = new Point( 10, 10 );
        System.out.println( p2 );
        
        System.out.println( "Egalité : " + ( p1 == p2 ) );
        System.out.println( "Hash code : " + p2.hashCode() );
        System.out.println( "Getters : " + p2.x() + " - " + p2.y() );
        
    }
    
}
Utilisation de votre record

Ajouter des membres dans un record

Vous pouvez, bien entendu, compléter un record avec vos propres membres (méthodes, constructeurs...).

Ajouter des constructeurs

Afin de proposer plus de facilité pour la construction des instances de votre type record, vous pouvez offrir plusieurs constructeurs.

si vous souhaitez rajouter un constructeur à zéro paramètre, le constructeur initialement proposé dans le record ne sera plus présent. Il faudra donc aussi le rajouter explicitement.
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
package fr.koor.samples;

public record Point(double x, double y) {
    
    public Point() {
        this( 0, 0 );
    }
    
    public Point( double x, double y ) {
      this.x = x;
      this.y = y;
    }
    
}
Exemple d'ajout de constructeurs dans un record

Comme vous le constatez, il est possible de directement utiliser les attributs x et y pour donner l'état initial à l'instance.

Testons maintenant notre constructeur à zéro paramètre.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
package fr.koor.samples;

public class Start {

    public static void main( String[] args ) {
        
        // Test du constructeur sans paramètre.
        Point p1 = new Point();
        System.out.println( p1 );
        
        // Test du constructeur à deux paramètres.
        Point p2 = new Point( 10, 10 );
        System.out.println( p2 );
        
        System.out.println( "Egalité : " + ( p1 == p2 ) );
        System.out.println( "Hash code : " + p2.hashCode() );
        System.out.println( "Getters : " + p2.x() + " - " + p2.y() );
        
    }
    
}
Exemple d'ajout de constructeurs dans un record

Ajouter des méthodes

Pour ajouter une méthode à votre type record, procédez comme pour une classe traditionnelle. Comme vous le constatez dans la capture d'écran ci-dessous, votre méthode peut accéder directement aux attributs de la classe, mais aussi aux méthodes d'accès en lecture.

L'assistance proposées par eclipse montre les attributs ainsi que les méthodes d'accès aux attributs.

Bien entendu, vous n'avez un accès qu'en consultation sur l'état de vos records. Effectivement, aucune méthode d'accès en écriture n'est disponible. De plus, les attributs x et y étant déclarés finaux (mot clé final) dans la classe, vous ne pourrez pas les modifier.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
package fr.koor.samples;

public record Point(double x, double y) {
    
    public Point() {
        this( 0, 0 );
    }
    
    public Point( double x, double y ) {
      this.x = x;
      this.y = y;
    }
    
    public void doSomething() {
        // Accès aux attributs du record.
        System.out.printf( "%f,%f with attributes\n", this.x, this.y );
        // Accès aux méthodes d'accès du record.
        System.out.printf( "%f,%f with accessors\n", this.x(), this.y() );
    }
    
}
Exemple d'ajout d'une méthode dans un record


Introduction à la POO et principe d'encapsulation Mise en oeuvre du concept d'héritage