JEP 390 Warnings for Value Based Classes

Contexte

Avant de parler de ces nouveaux avertissements, présentons le projet Projet Valhalla.

Ce projet travaille notamment sur deux fonctionnalités :

  • "inline type"

  • Generic over Primitive Types.

Le travail concerne l’amélioration de la prise en compte des types primitifs afin de réaliser des optimisations.

Dans le monde Java, tout est objet. Chaque type primitif correspond une classe "wrapper", comme java.lang.Integer, java.lang.Double pour respectivement les types int, double. La gestion des ensembles Set ou des listes List est un cas d’usage. En effet, nous ne pouvons pas écrire la définition suivante : Set<int>.

astuce Un autre cas d’usage est l’utilisation de ces classes dans les entités JPA qui permet de traiter la valeur nulle d’une colonne. En effet, en utilisant le type primitif, la colonne devient obligatoire.

Cependant, cela a un coût en mémoire et un coût en temps d’accès. Pour accèder à la valeur, il faut passer par référence qui demande un accès mémoire. Par exemple, un tableau d’entier int[] prend moins de places qu’un Set<Integer>. Sans compter que côté matériel, l’accès d’un tableau est plus efficace qu’un parcours d’un ensemble dont il est nécessaire d’accèder à la valeur par référence. En effet, cela nécessite un accès mémoire donc les caches et les pipelines des processeurs ne sont pas mis à profit.

De plus, le fait qu’ils soient des instances de classes, ils portent une identité qui n’a pas de sens pour les types primitives.

Value-based classes

Les "Value based classes" sont des classes qui ont les propriétés suivantes :

  • sont des classes finales et immutables,

  • possèdent les méthodes : equals(), hascode() et toString(),

  • l’égalité en utilisant la méthode equals() et non l’opération d’égalité (==),

  • n’ont pas de constructeurs mais une méthode fabrique "factory method" qui n’apporte aucun garantie sur l’identité.

avertissement L’impact est assez important. Donc, la transition se fait par étape. s

La première a été de désigner les classes candidates. Nous avons par exemple :

  • les classes "wrapper" des types primitives : java.lang (Byte, Short, Integer, Long, Float, Double, Boolean et Character)

  • les classes optionnelles : java.util (Optional, OptionalInt, OptionalLong et OptionalDouble)

  • certaines classes de l’API java.time (Instant, LocalDate, LocalTime, LocalDateTime, ZonedDateTime, ZoneId, OffsetTime, OffsetDateTime, ZoneOffset, Duration, Period, Year, YearMonth et MonthDay)

La seconde étape a été de déprécier les contructeurs des classes "wrapper" dans le JDK 9.

avertissement Les classes optionnelles`java.util` et les les classes de l’API java.time ne possèdent pas de constructeurs. Ils utilisent le principe des méthodes de fabriques.

ZonedDateTime maintenant = ZonedDateTime.now();
OptionalInt option = OptionalInt.of(15);

Warnings for Value Based Classes

L’objectif de la troisième étape est de décourager les mauvaises utilisations de ces classes en ajoutant des avertisssements à la compilation et à l’exécution.

Dans le JDK 16, les constructions des classes "wrapper" sont dépréciés pour suppression.

warning: [removal] Double(double) in Double has been deprecated and marked for removal
        d = new Double(20.0);
                ^

Pour rappel, nous avons deux posssibilités

Double d = Double.valueOf(10.0); // factory method
Double d = 10.0;		 // tout simplement :)

Une contrainte supplémentaire est qu’il ne sera plus possible d’utiliser ces classes pour les blocs synchronized. C’est pourquoi, l’objectif est d’ajouter un avertissement pour déconseiller cet usage.

Double d = 10.0;
synchronized (d) {
	System.out.println("Section synchronized (d)");
}

Lors de la compilation, nous obtenons le message suivant :

warning: [synchronization] attempt to synchronize on an instance of a value-based class
        synchronized (d)
        ^

Cependant, en fonction du code, cela ne peut pas être détecter lors de la compilation.

Double d = 10.0;
Object o = d;
synchronized (o) {
	System.out.println("Section synchronized (o)");
}

En effet, la définition d’un bloc synchronized avec la classe Object reste naturellement autorisée. Donc le compilateur n’affiche pas d’avertissement.

C’est pourquoi, des avertissements à l’exécution ont aussi été implémentés. Par contre, ces avertissements ne sont pas activés par défaut. Il faut ajouter l’option suivante sur la ligne :

  • -XX:DiagnoseSyncOnValueBasedClasses=1 Cela est plus qu`un avertissement puisque la JVM s’arrête avec une erreur fatale.

  • -XX:DiagnoseSyncOnValueBasedClasses=2 Celui-ci est un avertissement sur la console ou via les événements JDK Flight Recorder.

[0,046s][info][valuebasedclasses] Synchronizing on object 0x000000071721b3b0 of klass java.lang.Double
[0,046s][info][valuebasedclasses] 	at fr.lbenoit.billets.codes_sources.Programme.main(Programme.java:16)
[0,047s][info][valuebasedclasses] 	- locked <0x000000071721b3b0> (a java.lang.Double)
Section synchronized (d)
[0,047s][info][valuebasedclasses] Synchronizing on object 0x000000071721b3b0 of klass java.lang.Double
[0,047s][info][valuebasedclasses] 	at fr.lbenoit.billets.codes_sources.Programme.main(Programme.java:22)
[0,047s][info][valuebasedclasses] 	- locked <0x000000071721b3b0> (a java.lang.Double)
Section synchronized (o)

avertissement Il est à noter que pour utiliser ces options, il faut activer les diagnostics VM avec l’option -XX:+UnlockDiagnosticVMOptions

C’est l’occasion de se préparer au futur de Java.