Un peu d'histoire
Le langage Java s'appuie beaucoup sur les pointeurs. Tout objet manipulé est un pointeur vers sa référence, tout objet passé en paramètre de méthode (ou retourné) est un pointeur vers sa référence. Exception faite des types primitifs, qui sont eux directement des valeurs.
A l'époque de la création du langage, le coût d'un accès mémoire et d'une opération arithmétique était sensiblement le même. Aujourd'hui, le coût d'accès mémoire est 200 à 1000 fois plus grand qu'une opération. L'accès aux références, via de nombreuses indirections de pointeurs, finit par impacter significativement les performances.
Le projet Valhalla nous propose de se débarrasser de ces pointeurs pour les structures qui n'en ont pas besoin. Il propose aussi d'unifier les types primitifs et les objets.
Les 2 types en Java
Les objets
Tout objet en Java possède une identité (le fameux @123 dans le debugger). On ne peut pas copier un objet facilement. L'objet vit à un endroit précis (la heap) et pour modifier son état, il faut atteindre cet endroit. Ce que l'on manipule dans le code est un pointeur, qui lui est facilement copiable. L'opérateur == répond à la question "ces deux pointeurs pointent-ils vers la même identité ?"
Foo bar = new Foo(); //On instancie Foo et récupère un pointeur vers l'instance
Foo foobar = bar; //On copie le pointeur
bar == foobar; //true
Foo barfoo = new Foo();
bar == barfoo; //false les deux pointeurs ont des objets d'identité différente
La valeur par défaut d'un objet est null.
Les primitifs
Java propose aussi des types primitifs, huit en tout. Ils représentent des valeurs pures. Ils vivent directement dans la stack et sont facilement copiables. Un int 3 est complètement indifférenciable d'un autre int 3 et l'opérateur == va simplement comparer leur valeur.
int a = 3;
int b = 3;
a == b; //true
La valeur par défaut d'un primitif est 0.
L'utilisation des primitifs, bien qu'incohérente au sein d'un langage objet, permet de maximiser les performances et la simplicité d'utilisation. Le langage propose des objets wrappers ou boxes qui encapsulent ces types primitifs dans des objets, leur offrant du comportement.
Les Value Class, un entre deux
Le projet Valhalla introduit la notion de Value Class. L'idée est de garder la notion d'objet, mais de retirer la notion d'identité qui est un frein à l'optimisation. Beaucoup de classes du JDK n'en ont déjà pas besoin. C'est le cas par exemple de Optional ou DateTime.
Créons un objet valeur
public value class Point{
int x;
int y;
public Point(int x, int y){
this.x = x;
this.y = y;
}
public Point right(){
return new Point(x + 1, y);
}
}
Point est donc une classe dont les instances n'ont pas d'identité. En conséquence, la classe et ses champs sont implicitement final. En tant que classe, elle peut avoir des champs, des constructeurs, des types paramétrés, hériter d'une superclasse (avec des restrictions). On manipule toujours un pointeur et la valeur par défaut d'une value class est null.
Le comportement de l'opérateur == a changé pour les value class
Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
p1 == p2; //true car leur état est le même
Les value objects sont facilement copiables, faciles à déconstruire selon leur champs et à ré-aggréger. Cela permet au compilateur de faire des optimisation intéressantes.
Les Primitive Class, enfin la cohérence
On l'a vu, les value class apportent des optimisations d'empreinte mémoire et d'accès, tout en conservant les avantages d'être des objets. Elles sont les candidates idéales pour remplacer beaucoup de classes de valeur, sans identité. On peut vouloir aller encore plus loin et se rapprocher de la notion de type primitif. Une famille de classe qui ne pourrait pas être null, automatiquement initialisé à "0", dont on ne manipule pas un pointeur.
Le projet Valhalla introduit alors la notion de primitive class. L'idée étant d'avoir des objets qui vont se comporter comme des types primitifs.
primitive class Point implements Serializable {
int x;
int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
Point scale(int s) {
return new Point(s*x, s*y);
}
}
Notre classe Point se comporte maintenant comme un primitif. Il ne peut pas être null car ses champs, qui sont forcément des primitifs, sont bien initialisés. En manipulant un Point, on manipule directement les données et non un pointeur.
Lors de la sortie, on imagine que les wrappers de primitifs seront des primitive class. L'utilisation de type primitifs pourra alors devenir inutile.
Integer i = 3; //On aurait un vrai primitif ici
Pour conclure
Ce petit tour d'horizon du projet Valhalla nous montre dans quelle direction Java veut aller. La définition de Value Class et Primitive Class permettra non seulement des optimisations de l'empreinte et des accès mémoire, mais aussi une meilleure définition des objets. C'est effectivement une information capitale de savoir qu'un objet ne représente qu'une valeur sans identité, ou peut même être considéré comme un primitif.
Le sujet Java vous intéresse ? Découvrez tous les articles sur ce thème ici 👈