- La JEP 401: Value Classes and Objects a atteint le stade d’intégration dans une preview réelle du JDK
- L’objectif central est de permettre aux objets Java de « se coder comme une classe et fonctionner comme un int », afin de réduire les coûts liés aux en-têtes d’objet, à l’allocation sur le tas, au GC et à l’indirection par pointeurs
- Dans le JDK 28, la value class reste pour l’instant un type référence nullable ; les types non nuls, les génériques spécialisés et l’encodage 128 bits ne sont pas inclus, et
--enable-preview est requis
- La JVM peut scalariser un value object ou effectuer une flattening sur le tas dans des champs et des tableaux, mais dans des super-types comme
Object ou avec des génériques effacés, il peut être matérialisé comme un objet sur le tas
- Les développeurs Java devront intégrer la différence entre identity et value dans la conception de leur code, avec des conséquences sur
==, synchronized, les wrappers primitifs, les performances des tableaux et la future spécialisation des génériques
Le périmètre de Valhalla dans le JDK 28
- Le 15 juin, l’ingénieure Oracle Lois Foltan a confirmé l’intégration dans le dépôt principal d’OpenJDK de la JEP 401: Value Classes and Objects, avec un objectif JDK 28
- La pull request associée ajoute plus de 197 000 lignes réparties sur 1 816 fichiers
- L’ampleur des changements était telle qu’au cours de l’intégration, il a été demandé aux autres committers de suspendre temporairement les gros commits
- La JEP 401 est une fonction preview désactivée par défaut
--enable-preview est nécessaire pour utiliser la syntaxe
- Brian Goetz a précisé qu’il s’agissait de « la première partie de Valhalla »
- Le JDK 28 est prévu pour mars 2027, et l’intégration dans la branche principale est planifiée vers juillet 2026
Le coût du modèle objet Java visé par Valhalla
- Le slogan de Valhalla est « codes like a class, works like an int »
- L’objectif est de permettre à la JVM de traiter aussi efficacement qu’un primitive une classe ordinaire avec des méthodes, la validation du constructeur et des noms de champs explicites
- En Java, à l’exception des 8 primitives, presque tout est un type référence
- Dans
Point p = new Point(1, 2), p n’est pas le point lui-même, mais un pointeur vers un objet sur le tas
- À chaque lecture de champ, la JVM doit suivre ce pointeur
- Quand le nombre d’objets augmente, le coût explose rapidement
- Chaque objet possède un en-tête d’objet pour le type, l’état de synchronisation, etc.
- Les objets sont alloués sur le tas puis deviennent des cibles pour le GC
- Un tableau d’un million de
Point se compose en réalité d’un million de pointeurs et d’un million d’objets dispersés sur le tas
- Dans son texte « State of Valhalla », Brian Goetz qualifie cette disposition mémoire de fluffy
- Ce que vise Valhalla, c’est une disposition dense où les données sont stockées côte à côte
L’écart matériel et les limites de l’escape analysis
- Une disposition mémoire dense est importante à cause de l’écart de vitesse entre CPU et mémoire
- En 1995, le coût d’accès à la mémoire était comparable à celui des opérations CPU
- Aujourd’hui, les CPU sont d’un ordre de grandeur plus rapides que la mémoire principale, et ce sont les caches qui comblent l’écart
- Les CPU lisent généralement la mémoire par blocs de 64 octets cache line
- Quand les données sont compactes et ordonnées, beaucoup de valeurs utiles sont récupérées d’un coup
- En revanche, suivre des pointeurs vers des objets dispersés peut provoquer des cache miss, nettement plus lentes qu’un hit
- L’escape analysis de la JVM peut supprimer certaines allocations d’objets
- Si elle détermine qu’un objet ne « s’échappe » pas hors d’un fragment de code local, elle peut éviter l’allocation sur le tas et décomposer ses champs en variables ou en registres
- Mais l’escape analysis reste peu prévisible et fragile
- Si l’objet est placé dans le champ d’une autre classe, stocké dans un tableau, passé à une méthode complexe ou franchit une frontière que le JIT ne peut pas analyser, l’optimisation peut s’arrêter
- Un petit refactoring, une mise à jour du JDK ou un changement de structure du code peuvent suffire à renvoyer l’objet sur le tas
- Abandonner les objets au profit d’un encodage direct en octets bruts comme
r, g, b peut améliorer les performances, mais au prix de la sécurité, de la lisibilité, de la validation et des méthodes
Départ en 2014 et transition de Q World à L World
- Le Project Valhalla a officiellement démarré en 2014
- James Gosling l’avait alors décrit comme « six PhDs tied into a single knot »
- Les créateurs de Java voulaient des value types dès l’époque de Java 1.0, mais en 1995 le problème était trop difficile et ils ont renoncé
- L’objectif initial était de rétablir l’alignement entre le modèle de programmation et les caractéristiques de performance du matériel moderne
- L’idée était de permettre aux utilisateurs de déclarer eux-mêmes des types plats et denses comme des primitives, tout en conservant l’apparence et le comportement de classes ordinaires
- Les premiers prototypes suivaient l’approche Q World
- Ils traitaient le nouveau value type comme une entité fondamentalement différente des objets, avec un descripteur de type, un bytecode et un top type séparés
- Toute la hiérarchie de types de la JVM devait alors supporter deux variantes, ce qui augmentait la complexité
- L’arrivée de L World vers 2019 a marqué un tournant
- Les value types y partagent le même « L carrier » que les références ordinaires
- L’équipe pensait que cette intégration serait difficile, mais elle a fonctionné sans compromis majeur et a résolu plusieurs problèmes des prototypes précédents
- L World a introduit une séparation importante
- Le modèle JVM et le modèle du langage n’ont pas besoin de se superposer à 100 %
- La JVM peut adopter le modèle L World, tout en exposant aux programmeurs un modèle de langage plus pratique
- Depuis, le travail est divisé en deux étapes : les value classes et les génériques spécialisés
Évolution des noms et du modèle
- La terminologie de Valhalla a changé plusieurs fois, et il ne s’agissait pas simplement de renommages, mais de changements de modèle.
- Le terme initial était value types.
- À l’époque, la nature exacte de ces types n’était pas encore clairement définie.
- Vers 2019-2020, le modèle des inline classes s’est imposé.
- Les classes existantes étaient distinguées comme identity classes, et les nouvelles classes comme inline classes sans identité.
- Une inline class était par défaut final, ses champs étaient final, et elle ne pouvait pas être utilisée pour la synchronisation.
- En 2021, le « State of Valhalla » abordait les primitive classes et un modèle à deux projections.
- L’idée était qu’un même type possède une variante value, aplatie et non nullable, ainsi qu’une variante reference autorisant
null.
- Des syntaxes comme
Point.val / Point.ref, puis Point! / Point?, ont aussi été expérimentées.
- Ce modèle était puissant, mais il imposait une charge cognitive importante.
- Les programmeurs devaient comprendre au quotidien les deux formes d’un même type et le moment où les conversions intervenaient.
- Au final, ce dualisme a été réduit pour simplifier le modèle côté utilisateur.
- Aujourd’hui, le JEP 401 utilise les notions de value class et de value object.
- Une value class se déclare avec le modificateur
value.
- Les instances sont des value objects sans identité.
- Une value class reste malgré tout un reference type.
- La non-nullabilité a été séparée dans un JEP optionnel distinct, Null-Restricted Value Class Types.
- Il n’est pas inclus dans le JDK 28.
- Les anciens articles décrivant le modèle des « primitive classes » peuvent donc différer de l’état actuel d’OpenJDK.
- Le JEP 401 est aussi accompagné du JEP en preview JEP 402: Enhanced Primitive Boxing.
- Il vise à rendre plus fluides les conversions entre primitive et wrapper.
- Il ne faut pas supposer que tout arrivera d’un bloc, dans une forme finalisée, en même temps que le JEP 401.
Le modèle de value class dans le JDK 28
- Une value class se déclare avec le modificateur
value.
value class USDCurrency implements Comparable<USDCurrency> {
private int cents; // implicitly final
public USDCurrency(int dollars, int cents) {
this.cents = dollars * 100 + cents;
}
public USDCurrency plus(USDCurrency that) {
return new USDCurrency(0, this.cents + that.cents);
}
// dollars(), cents(), compareTo(), toString()...
}
- Les value records sont également possibles.
- Les principales règles sont les suivantes :
- tous les champs d’instance sont implicitement final ;
- une méthode ne peut pas être
synchronized ;
- la classe est final par défaut ;
- des hiérarchies composées de value class et d’abstract value class sont possibles ;
- elle ne peut pas hériter d’une classe ayant une identité ;
- elle peut implémenter des interfaces.
- La propriété centrale est l’absence d’identité.
- Pour un objet classique, même avec le même contenu, deux appels à
new Point(1, 2) produisent deux objets distincts.
- Un value object n’a pas d’identité, tout comme il n’existe pas « deux 4 différents » pour la valeur
int 4.
Changements autour de ==, synchronized et null
- Pour un value object,
== ne compare pas l’identité, mais vérifie la substituabilité.
- La comparaison vérifie récursivement qu’il s’agit de la même classe et que les champs ont les mêmes valeurs.
- Les champs primitifs sont comparés au niveau des bits, et les champs objets sont à nouveau comparés avec
==.
new USDCurrency(3,95) == new USDCurrency(3,95) devient donc true.
- Cela dit, comme
== examine l’état interne, equals reste généralement plus adapté pour savoir si deux objets représentent « la même donnée ».
- Un value object n’a pas d’identité sur laquelle synchroniser.
- Tenter une synchronisation provoque une IdentityException.
- Lorsqu’il faut vérifier explicitement la présence d’une identité, on peut utiliser
Objects.requireIdentity et Objects.hasIdentity.
- Dans le JDK 28, une value class reste nullable.
USDCurrency d = null; est légal.
- Les types interdisant
null relèvent d’un futur JEP distinct et ne font pas partie du JDK 28.
- La non-nullabilité n’est pas qu’une question de syntaxe : elle devient aussi un levier de performance ouvrant la voie à un aplatissement plus large des value classes.
Scalarisation et aplatissement sur le tas
- Le JEP 401 donne à la JVM la liberté d’optimiser les value objects.
- La scalarisation (scalarization) est une technique JIT qui décompose une référence vers un value object en un ensemble de champs.
- Au lieu de passer un pointeur vers
Color, on peut transmettre les octets r, g, b et un flag indiquant la nullité.
- Les coûts d’allocation et de GC peuvent alors disparaître.
- C’est proche de l’escape analysis, mais plus prévisible, et cela peut s’appliquer au-delà des frontières d’appels de méthodes non inlinées.
- La scalarisation a toutefois des limites.
- En général, elle ne fonctionne pas si le type de la variable est
Object, qui est un supertype de la value class, ou un paramètre générique effacé.
- Dans ce cas, l’objet doit être matérialisé sur le tas.
- L’aplatissement sur le tas (heap flattening) consiste à encoder directement les valeurs de champ d’un value object dans un vecteur de bits compact, écrit dans un champ ou une cellule de tableau.
- Il n’est plus nécessaire d’avoir un pointeur vers un autre emplacement mémoire sur le tas.
- Cela améliore la densité des données et la localité.
- Les données aplaties doivent pouvoir être lues et écrites de manière atomique afin d’éviter le tearing lors des accès concurrents.
- Sur les plateformes courantes, une taille « suffisamment petite » peut se situer autour de 64 bits, flag de nullité compris.
- Les petites value classes peuvent donc être bien aplaties, mais même deux champs
int ou un seul double peuvent dépasser la taille d’écriture atomique et finir comme objets classiques sur le tas.
- À l’avenir, un encodage sur 128 bits et les types null-restricted pourraient permettre l’aplatissement de value classes plus volumineuses.
Effets sur le boxing, les wrappers et les tableaux
- Quand le mode preview est activé, les classes wrapper des primitifs comme
Integer, Long et Double deviennent elles-mêmes des value classes.
- Comme la box perd son identité, la JVM peut la scalariser et l’aplatir.
Integer[] peut ainsi se rapprocher de l’efficacité de int[], avec une forte réduction des surcoûts liés au boxing.
- Le JEP 402: Enhanced Primitive Boxing étend encore les conversions entre primitifs et box.
- Il ouvre la voie à des expressions comme
List<int>, mais cela reste un chantier distinct encore en maturation.
- C’est dans les tableaux que l’effet est le plus visible.
- Un
Color[] classique peut devenir un million de pointeurs vers un million d’objets dispersés sur le tas.
- Un
Color[] où Color est une value class peut devenir un bloc contigu stockant directement les valeurs de couleur.
- Le CPU peut alors lire plusieurs valeurs successives par ligne de cache.
Avant/après avec l’exemple de Point[]
- Voici un exemple de classe classique avant Valhalla :
final class Point {
final int x;
final int y;
Point(int x, int y) { this.x = x; this.y = y; }
}
Point[] points = new Point[1_000_000];
- Ce tableau contient un million de pointeurs
- Chaque pointeur référence un objet
Point distinct quelque part dans le heap
- Chaque objet possède aussi un en-tête d’objet en plus des deux
int
- Lors d’un parcours, il faut lire le pointeur, sauter à cette adresse, puis lire les champs
- Après Valhalla, un exemple de value class ressemble à ceci
value class Point {
final int x;
final int y;
Point(int x, int y) { this.x = x; this.y = y; }
}
Point[] points = new Point[1_000_000];
- La différence dans le code tient en un seul mot,
value, mais l’agencement mémoire change
- La JVM peut stocker les valeurs de chaque point de façon compacte directement dans le tableau
- Il n’y a ni en-tête par élément ni pointeur
- Pour
x et y, les deux int peuvent être placés de manière contiguë sur 8 octets, avec éventuellement un indicateur de nullité
- La maintenabilité est elle aussi préservée
Point reste une class avec un nom, un constructeur, une validation et des méthodes
- On peut éviter de découper en
int[] xs, int[] ys et de réaligner les index
Pourquoi les generics spécialisés restent encore à faire
- Les generics Java sont implémentés via le type erasure
List<String> et List<Integer> sont le même List à l’exécution
- Le paramètre de type
T est effacé en Object
- L’erasure était un choix délibéré pour introduire les generics sans casser la base de code existante de Java
- Transformer une class non générique en class générique n’a pas cassé les fichiers source ni les classes déjà compilées
- Valhalla et l’erasure entrent en conflit du point de vue des performances
- Si on met un value object dans
List<Point>, T étant effacé en Object, l’objet doit être matérialisé dans le heap
- Le bénéfice de l’aplatissement obtenu avec
Point[] peut disparaître dans ArrayList<Point>
- Le plan de rattrapage se fait en deux étapes
- Universal Generics : au niveau du langage, permettre aux variables de type de gérer aussi les value types
- Cela utilise toujours l’erasure
- Comme un champ
T commence par défaut à null, cela peut produire un avertissement du compilateur de type « null pollution »
- Une fois ces avertissements traités, l’API devient proche d’un état prêt pour la spécialisation
- Specialized Generics : au niveau de la JVM, générer un layout de class spécialisé pour chaque argument de type concret
- Dans la terminologie du projet, les notions de species et de type restriction sont liées à cela
- C’est seulement à ce stade que
ArrayList<Point> pourra réellement utiliser une mémoire plate
- Il n’y a pas de full specialized generics dans le JDK 28
- Le fait que les collections, les streams et les API deviennent plats et sans allocation avec les value types relève d’une version future
Ce qu’il y a et ce qu’il n’y a pas dans le JDK 28
- Ce qui arrive dans le JDK 28 est le suivant
- les déclarations
value class et value record
- la migration en value class de certaines value-based class existantes du JDK, comme les wrappers primitifs
- la scalarisation et l’aplatissement des classes qui remplissent les conditions
- un boxing moins coûteux
- Ce qui n’est pas dans le JDK 28 est le suivant
- les types à null restreint
- les full specialized generics
- l’encodage sur 128 bits
- un JEP 402 totalement mûr
- Comme il s’agit d’une preview feature, la syntaxe et le comportement peuvent changer d’une release à l’autre selon les retours
- Le JDK 28 n’est pas une LTS
- La prochaine LTS sera probablement le JDK 29, en septembre 2027
- Beaucoup d’entreprises découvriront sans doute une version stabilisée de Valhalla sur une LTS, mais la preview du JDK 28 lance déjà la boucle de feedback sur du vrai code
Ce qui va changer dans l’écosystème et dans le code
- Dans les domaines Java haute performance, Valhalla ouvre une voie pour manipuler des données denses sans renoncer à l’abstraction
- Cela concerne notamment le traitement de données, le calcul vectoriel, le ML, le développement de jeux, la finance ou les codecs
- Les frameworks et les bibliothèques peuvent commencer la migration des value-based class
- Le code qui dépendait de l’identité peut voir son comportement changer
== sur un value object ne compare plus une adresse, mais la substituabilité
synchronized sur un value object conduit à une IdentityException
- Même si
Integer devient une value class, le binaire continuera à se lier dans la plupart des cas
- La nouvelle erreur à la compilation concerne les cas où l’on tente de se synchroniser sur ce type
- Les usages de
== dépendant de l’identité de Integer, ou synchronized(someInteger), peuvent être affectés
- Une build early-access est disponible sur jdk.java.net/valhalla
Récapitulatif des questions fréquentes
- Une value class est différente d’un record
record correspond au choix de faire du contenu les components
value correspond au choix de renoncer à l’identité
- Les combinaisons class ordinaire, record, value class et value record sont toutes possibles
- Un value object peut être comparé avec
==
- Le sens n’est pas la comparaison d’adresse, mais la substituabilité
- Pour l’égalité des données représentées,
equals est généralement plus approprié
- Les value class du JDK 28 peuvent être null
- Les types non nullables relèvent d’un futur JEP
- C’est aussi important pour l’aplatissement de value class plus grandes
- Un
ArrayList<Point> rapide et plat, ce n’est pas encore pour maintenant
- À cause du type erasure, les objets dans les collections génériques doivent être matérialisés dans le heap
- Dans le JDK 28, les cas représentatifs où l’aplatissement fonctionne directement sont les champs de value type et les tableaux comme
Point[]
- L’escape analysis ne remplace pas tout
- Si l’objet sort dans un champ, un tableau ou au-delà de la frontière de l’analyse, l’optimisation peut se casser
- La scalarisation d’un value object est plus prévisible et peut aller plus loin au-delà des frontières d’appel de méthodes
- L’ensemble de Valhalla s’étend sur plusieurs releases
- Le JDK 28 est la première preview des value class
- Les specialized generics, les types à null restreint et l’encodage 128 bits sont des chantiers pour les releases futures
2 commentaires
Les threads virtuels de Project Loom, publiés après une longue période de développement, sont pratiques et règlent beaucoup de choses au niveau du runtime JVM, ce qui laisse en général moins de sujets de préoccupation aux développeurs.
J’espère que Project Valhalla offrira lui aussi, une fois sa version finale publiée, une impression proche d’un déjeuner gratuit.
Commentaires Hacker News
On disait que la différence de mémoire était fondamentale, mais je me demande si l’article a vraiment été relu correctement.
Il n’expliquait pas juste avant que les objets dont la représentation dépasse 64 bits ne peuvent pas être aplatis sur le tas ? Le
Pointde l’exemple a bien 2 entiers de 32 bits plus un indicateur de nullité, donc au minimum 65 bits.Entre la formule « il peut y avoir un indicateur de nullité » et les courtes phrases mises en avant juste après, on a l’impression qu’une IA a essayé de produire des punchlines avant de partir dans une autre direction, et le bloc
"[IMAGE: the same Point[] array in two variants..."au milieu est regrettable.Utiliser l’IA pour aider à écrire, très bien, mais si on n’y met pas sa propre voix, il n’y a aucune raison de lire ça.
https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing#...
Mais après quelques paragraphes, il est devenu évident que c’était un texte passé par un LLM, ou pire encore.
Blog technique ou pas, j’aimerais vraiment qu’on arrête de laisser l’IA écrire à notre place. Personne n’a envie de lire ce genre de texte.
Aujourd’hui, j’ai découvert qu’en Rust il existe
NonZeroU64, et qu’en le combinant avecOptional, on peut obtenir le comportement voulu avec seulement 64 bits par élément.https://doc.rust-lang.org/std/num/type.NonZeroU64.html
Comme c’est clairement indiqué dans la JEP, ce n’est que la première livraison d’une fonctionnalité énorme, et comme beaucoup de fonctionnalités récentes de Java, elle arrive par morceaux.
L’objectif est évidemment d’aplatir aussi des valeurs plus grandes, et le mécanisme existe déjà dans la JVM. Il reste simplement à exprimer au niveau du langage l’intention « on autorise la déchirure ».
Je reconnais l’ampleur du travail réellement accompli sur Valhalla, mais j’ai du mal à adhérer à l’idée que « le modèle était puissant mais mentalement lourd ».
Dire qu’une variable ne peut pas être nulle n’est pas une distinction mentalement pénible, surtout si tout est suffisamment explicite.
L’attitude consistant à « simplifier le modèle utilisateur même au prix du plafond de performance » a peut-être au contraire simplifié les choses pour l’utilisateur.
Le système de types d’un langage de programmation sert à offrir aux développeurs des garanties pratiques au-dessus d’un CPU qui, lui, ne manipule que des nombres. Il n’y a pas besoin de réduire des garanties de sûreté optionnelles au motif qu’elles seraient « trop complexes ».
Et cela alors même qu’on en est venu à reconnaître que « le modèle du langage et celui de la JVM n’ont pas besoin de se superposer à 100 % ».
La gouvernance de Java semble défaillante, surtout comparée à .NET qui, lui, a pris des décisions globalement justes dès le départ.
Je me demande même si Java a encore de la valeur ou suscite encore de l’intérêt chez Oracle de nos jours. L’entreprise donne désormais l’impression d’être une activité datacenter/compute à laquelle s’accrochent des activités historiques et une dette colossale.
Par moments, on dirait que les seules divisions encore rentables chez Oracle sont l’équipe juridique et la tonte de pelouse.
Et un marqueur de nullité est aussi prévu : https://openjdk.org/jeps/8303099
Il faut simplement livrer tout cela progressivement, et rien que cette PR qui introduit les value classes/objects fait déjà 200 000 lignes.
Il ne me semble pas qu’on dise que c’est impossible ; c’est plutôt qu’on ne peut pas manger un éléphant d’une seule bouchée.
Cela dit, ils rongent quand même cette seule patte depuis assez longtemps.
nullable, dans la programmation orientée rails, n’est qu’un état de chargement différent.Si c’est un concept considéré comme résolu depuis 2012, il n’y a aucune raison d’intégrer directement plusieurs variantes d’état dans le langage. Les rails vont soit vers A, soit vers B, selon l’état de chargement du train.
Quand un concept apparaît puis disparaît et déclenche des guerres de langage, c’est le signe qu’il existe une demande que les langages ne savent pas bien traiter, ou qu’ils traitent au prix d’une surcharge mentale.
Des choses comme
Value,Errorstates,Null,IoExceptions,WeirdOsStatesNeededToHandleUpstairs.https://fsharpforfunandprofit.com/rop/
Pour le dire à la Monty Python : avançons maintenant.
Integer/int.L’équipe Valhalla, au lieu de prévoir pour chaque type une projection avec identité et une autre sans identité, a choisi de faire en sorte que les value types n’aient tout simplement pas d’identité, si bien que
Integeretintdeviennent synonymes.La disposition mémoire est alors décidée automatiquement selon le contexte et les choix d’optimisation. C’est pour cela que la sémantique de
==pour les wrappers primitifs commeIntegera aussi changé, et ne dépend plus de l’usage d’une « projection référence » ou d’une « projection valeur ».Ce n’est pas ici qu’on a réduit des garanties de sûreté optionnelles au motif qu’elles seraient « mentalement pénibles ».
Dans les commentaires HN sur Java/JVM, on voit revenir sans cesse le fait qu’un nombre étonnamment élevé de gens ont une image ancienne de la JVM ou de Java, mais ne connaissent presque rien de ce à quoi cela ressemble aujourd’hui
La JVM de 2026 est un prédateur en pleine forme. A-t-elle des défauts ? Bien sûr, mais ses fondations sont extrêmement solides
Au travail, j’utilise Java 26 récent et les fonctionnalités en preview, surtout
StructuredConcurrency, et c’est excellent. Même après avoir utilisé Haskell et Python dans mes précédentes boîtes, je ne le regrette absolument pasPersonnellement, je connais les nouvelles fonctionnalités sorties ces dernières années, mais dans le travail réel, Java est littéralement coincé dans le passé
Une bonne partie des commentaires ici est assez injuste par rapport à l’excellent travail en cours et aux JEP encore plus intéressantes à venir
Si on compare Java à un enfant, il a grandi ses premières années avec des parents aimants (Sun), puis a été jeté dans un garage avec les autres enfants et laissé à l’abandon par un tuteur malveillant (Oracle)
Il a été négligé et peu aimé jusqu’au JDK 8, donc il a essentiellement dû rattraper son retard
Dire que « ce n’est que maintenant qu’on a enfin des structs ou des value types » est vrai, mais c’est parce que sa croissance a été freinée par des processus d’entreprise énormes, bureaucratiques et hostiles. Maintenant, il est libre et aimé via la famille OpenJDK
On continuera à profiter du plaisir de write once, deploy anywhere à l’avenir
C’est plus proche d’une histoire où des parents aimants l’ont élevé, puis l’ont confié à une famille d’accueil à cause de problèmes financiers, où il a été négligé
Ensuite, il a été adopté par de nouveaux parents aimants, Oracle, et Java s’est épanoui pour devenir un adulte sain et stable
C’est aussi Oracle qui a fait d’OpenJDK l’implémentation de référence et achevé l’open source de la plateforme, ainsi que l’open source d’outils auparavant propriétaires comme JFR et Mission Control
Oracle a aussi conservé beaucoup de membres fondateurs de l’équipe langage, ce qui est assez rare dans ce type de rachat, et Java s’est beaucoup amélioré à la fois côté langage et côté runtime
Oracle a fait avancer Java à une vitesse sans précédent tout en conservant l’essentiel de la rétrocompatibilité
On dit que .NET « l’a bien fait dès le départ », mais si cela désigne la séparation et la réécriture entre .NET Framework/.NET Core/.NET, alors même dans cette discussion cela n’a pas de sens. .NET aurait pu apprendre de Java et a pourtant raté certaines choses
Même chose pour MySQL. Sur ce site, on le disait « mort », mais pour les gens qui connaissent vraiment le sujet, il a été relancé sous Oracle
La dernière version de Java sous Sun est sortie en 2006, Oracle a racheté Sun en 2010, JDK 7 est sorti en 2011 et JDK 8 en 2014
L’équipe est globalement restée la même, la principale différence étant qu’Oracle a mis fin à la négligence et apporté davantage de financement. C’est pour cela que le rythme de Java s’est accéléré après le rachat
On parle de « rattraper son retard », mais il n’est même pas clair par rapport à qui. Les seuls langages aussi populaires ou plus populaires que Java sont JS/TS et Python
Ceux qui disent que Java est en retard le comparent souvent à des langages qui s’en sortent bien moins bien que Java. Les gens attachés à certaines fonctionnalités oublient parfois que les langages qui les ont stagnent non pas grâce à ces fonctionnalités, mais malgré elles
Les managers aiment les sorties rapides, alors que le leadership technique, depuis l’époque Sun, insistait plutôt sur le fait qu’il fallait faire les choses prudemment, lentement et correctement
Je comprends l’idée selon laquelle Java n’est plus aussi populaire qu’en 2003, mais cette période était exceptionnellement unifiée, non seulement pour Java mais pour tout l’écosystème logiciel, et on n’a jamais retrouvé un tel niveau d’unification avant ni après
La vraie technologie write once, deploy anywhere aujourd’hui, c’est WebAssembly. La JVM a eu sa chance, et elle a perdu
Cela dit, je n’irais pas jusqu’à dire que Java a eu sa « croissance freinée ». Des choix ont été faits, certains étaient raisonnables, d’autres non, et ces choix sont très difficiles à corriger plus tard
Il suffit de regarder C++ : à mes yeux, sa semi-compatibilité avec C est un albatros de 150 pieds impossible à corriger, et beaucoup de versions depuis C++11 ont surtout consisté à rendre cet albatros un peu plus supportable
Sur la JVM, traiter toutes les classes de valeur comme un unique type L à la manière des types primitifs me semble être une solution assez élégante à un problème difficile
Au fond, tout cela découle de la décision prise à l’époque de Java 2 d’implémenter les génériques par effacement de types pour préserver la rétrocompatibilité, et C3 a vu le résultat avant de refuser cette voie
Rien que l’évolution des value types de Java pourrait faire l’objet d’un thriller technique entier
J’ai lu la mailing list et regardé toutes les vidéos liées, et le processus qui a permis d’unifier la conception en quelque chose qui reste toujours typiquement Java est vraiment impressionnant
En même temps, cela est allé beaucoup plus loin dans la définition de ce que signifient les value types et des optimisations possibles, ainsi que de l’endroit où elles peuvent l’être
valueAvec les classes-valeurs,
==finit en pratique par se comporter commememcmp()C’est un peu regrettable, car cela brise l’encapsulation et expose des détails d’implémentation
Le code client peut bifurquer selon la façon dont une valeur donnée est représentée en interne. D’une certaine manière, c’est encore pire qu’une comparaison d’identité, parce qu’au moins une comparaison d’identité n’expose pas l’état interne
Ce n’est pas une nouvelle manière de faire de l’orienté objet classique, mais la façon dont un langage né de l’idéologie objet fait un pas de plus vers un monde post-objet
J’imagine que côté Java ils ont forcément envisagé des choses comme exclure le padding de la comparaison, ou forcer les octets de padding à 0
Cela doit aussi fonctionner pour les chaînes. Les chaînes continueront évidemment à être allouées sur le tas, et faire un
memcmpd’un pointeur dans une nouvelle « struct » revient exactement à une comparaison d’identitéJava sépare la vérification de l’identité des objets et la vérification d’égalité.
==regarde fondamentalement si deux pointeurs sont les mêmes, tandis que l’égalité est une notion subjective fondée sur des interfaces commeequals/hashCodeAinsi,
new Integer(1000) == new Integer(1000)était autrefoisfalse, mais devient maintenanttrue, etnew Integer(1000).equals(new Integer(1000))vauttrue, tandis quenew Integer(10) == new Long(10)était autrefoisfalse, mais devient maintenant une erreur de compilationDans l’ancien Java, les entiers en dessous d’une certaine valeur étaient parfois remplacés par un type canonique, probablement autour de 128 si je me souviens bien. C’est ce qui créait la différence entre 10 et 1000
Maintenant, ces comparaisons semblent être implicitement unboxed. Le fait que la comparaison
Integer/Longsoit passée defalseà une erreur de compilation montre bien qu’il y a de l’unboxing en jeuIl est peut-être encore possible d’obtenir l’ancien comportement avec des variables
Quoi qu’il en soit, une fois qu’une classe-valeur perd son identité,
==passe d’une égalité de pointeurs à une égalité bit à bit. J’espère qu’ils régleront ces nombreux cas limites, mais techniquement c’est bien un changement cassantJe comprends l’intention des classes-valeurs, mais l’implémentation est défectueuse
Qu’affiche le code suivant ?
Point a = new Point(10, 10); Point b = a; a.x = 100; System.out.println(b.x);Jusqu’ici, la réponse était claire, mais avec l’ajout des classes-valeurs, la réponse change selon que
Pointest une classe-valeur ou une classe-référence. Cette conception nuit donc à la lisibilitéCela viole le principe d’uniformité. Dans The Psychology of Computer Programming de Weinberg, l’uniformité est décrite comme le principe psychologique selon lequel l’utilisateur s’attend à ce que ce qui se ressemble se comporte de manière similaire, et que ce qui est différent se comporte différemment
Quand un langage de programmation autorise, au point d’usage, deux syntaxes presque identiques à avoir des comportements sémantiquement différents, cela augmente la charge cognitive du lecteur. Il faut vérifier la déclaration de type ou s’en remettre aux outils pour savoir si l’affectation, l’égalité, l’identité et la mutabilité se comportent comme pour un objet-référence ordinaire ou comme pour une valeur
On aurait pu corriger cela en imposant aussi le mot-clé
valueau point d’usage, et pas seulement au moment de la déclaration. Par exemple :value Point a = new Point(10, 10);https://openjdk.org/jeps/401
finalBien sûr, avec seulement ces quatre lignes, il n’y a aucun moyen de deviner que cela se passerait ainsi, mais ce problème existe déjà. La même chose se produirait si
Pointétait un recorda.x = 100;ne sera pas valide. Les types record sont immuablesDonc la situation redoutée ne devrait pas pouvoir se produire
Avec une écriture du genre
value Point a = new Point(10, 10); value Point b copy= a; a.x uniq= 100; System.out.println(b.x);, il serait bien plus clair qu’il y a clonage/copie, et qu’une modification de champ n’affecte pas le champ d’un autre objetJe sais que dans l’univers Java, reconnaître l’existence de .NET est presque une impolitesse, mais je me demande en quoi cela diffère des structs .NET
En survolant les types-valeurs, la spécialisation des génériques et le boxing, on dirait qu’ils ont fait les mêmes choix
Là où C# a plus ou moins copié C dans une perspective bas niveau, Java aborde le sujet à un niveau plus élevé et analyse en détail quelles contraintes apportent quels avantages
Dans d’autres langages, la distinction struct/classe est binaire, alors que Java permet un contrôle plus fin afin de refléter la sémantique du domaine sous-jacent
Et il apparaît que les structs comportent divers pièges à tir dans le pied, notamment dans les contextes parallèles
Personnellement, je vois les structs C/C# comme mutables et passées par copie, tandis que les classes-valeurs sont immuables et passées par valeur
Je pense qu’en Java, l’allocation sur la pile n’est pas possible
La fausse dichotomie du genre « les structs de C# ont une identité et de la mutabilité, donc il faut définir précisément une sémantique de copie à l’affectation ou au passage, ce qui impose un modèle plus lourd au programmeur et laisse moins de liberté au runtime » correspond mal à ce qui est expliqué
Il n’y aura peut-être pas d’identité au sens de la sémantique de référence des classes Java, mais au sens d’une structure mémoire unique à une adresse donnée, il y a évidemment toujours une forme d’identité. Cela relève presque du pinaillage terminologique autour du vocabulaire Java
La note 6, « en quoi cela diffère-t-il des
structde C# ? », est inexacteVu que l’article contient quantité d’images générées par IA, on en vient à se demander s’il n’y a pas aussi beaucoup d’hallucinations dans l’écriture, ou au minimum dans la recherche
Le texte est un peu flou et dramatique, mais heureusement les documents originaux sont assez faciles à lire
Page principale : https://openjdk.org/projects/jdk/28/spec/
Statut des JEP : https://bugs.openjdk.org/secure/Dashboard.jspa?selectPageId=...
J’aimerais bien que quelqu’un suive aussi les évolutions connexes en C#, Swift, Java et Rust. Tous essaient de rattraper le matériel et, à mon avis, s’influencent mutuellement
Personnellement, je m’inquiète surtout de l’impact que ces changements auront sur le partage de mémoire FFI