juillet
2005
Pour vous préparer à la Certification Java SCJP 5.0, nous vous proposons quelques fiches.
Aujourd’hui, en voici une concernant l’autoboxing/unboxing
L’AutoBoxing/UnBoxing
La fonctionnalité d’autoboxing / unboxing est une fonctionnalité qui fut très attendue par certains programmeurs. Mais l’introduction de cette fonctionnalité peut également vous jouer de mauvais tours si vous n’y prenez garde.
AutoBoxing
L’autoboxing est l’opération qui consiste à envelopper automatiquement une primitive dans sa classe enveloppe correspondante.
Au lieu d’être obligé d’écrire du code semblable à ceci:
Integer i = new Integer(1000);
Boolean b = new Boolean(true);
Character c = new Character('A');
vous pouvez maintenant écrire le code suivant:
Integer i = 1000;
Boolean b = true;
Character c = 'A';
Ce qui est, vous le reconnaitrer, tout de même plus pratique, pour encoder.
Mais attention: l’autoboxing introduit une petite subtilité.
Il faut bien comprendre quelle est le code qui est généré par l’autoboxing.
Lorsque vous écrivez le code source suivant
Integer i = 100;
Boolean b = true;
Character c = 'A';
Le bytecode généré ne correspond pas du tout au bytecode généré pour le code source suivant:
Integer i = new Integer(100);
Boolean b = new Boolean(true);
Character c = new Character('A');
Il correspond plutôt au bytecode généré pour le code source suivant:
Integer i = Integer.valueOf(100);
Boolean b = Boolean.valueOf(true);
Character c = Character.valueOf('A');
Et vous devez vraiment penser au fait que le compilateur travaille comme cela. Car cela peut avoir un énorme impact sur votre code.
Imaginer le code suivant:
public class ComparerDesBooleens {
public static void main(String[] args) {
//Bloc 1: Comparer des Boolean en utilisant les constructeurs
{
Boolean b = new Boolean(true);
Boolean b2 = new Boolean(true);
if (b==b2) {
System.out.println("b == b2");
}
else {
System.out.println("b <> b2");
}
}
//Bloc 2: Comparer des Boolean en utilisant valueOf(boolean)
{
Boolean b = Boolean.valueOf(true);
Boolean b2 = Boolean.valueOf(true);
if (b==b2) {
System.out.println("b == b2");
}
else {
System.out.println("b <> b2");
}
}
//Bloc 3: Comparer des Boolean en utilisant valueOf(String)
{
Boolean b = Boolean.valueOf("true");
Boolean b2 = Boolean.valueOf("true");
if (b==b2) {
System.out.println("b == b2");
}
else {
System.out.println("b <> b2");
}
}
//Bloc 4: Comparer des Boolean en utilisant l'autoboxing
{
Boolean b = true;
Boolean b2 = true;
if (b==b2) {
System.out.println("b == b2");
}
else {
System.out.println("b <> b2");
}
}
}
}
Le résultat à la console sera
b <> b2
b == b2
b == b2
b == b2
Pourquoi ?
Le fait d’appeler un constructeur, on crée toujours un nouvel Objet en mémoire. Et lorsqu’on compare les deux références, elles pointes vers deux objets bien distincts.
Par contre, les deux méthodes valueOf() ont été optimisées. En effet, la classe Boolean tient en cache deux objets: TRUE et FALSE. Et les méthodes valueOf() ont été optimisées pour toujours retourner une référence à l’un de ces deux objets.
Et l’autoboxing a été implémenté de telle façon qu’en réalité on appelle la méthode valueOf().
Nous venons de voir des exemples avec la classe Boolean qui possédait en effet une méthode valueOf(boolean). Mais les autres classes primitives, comme Integer ou Float, ne possedent pas de méthode valueOf(int) ou valueOf(float), me direz vous. En effet, cela était vrai jusqu’à présent. Mais depuis le JDK 5.0, ces classes possèdent ces méthodes. Et ce sont ces méthodes qui sont appelées lorsque vous utilisez l’autoboxing.
Si on reprend donc l’exemple précédent utilisé pour la classe Boolean et qu’on l’adapte pour la classe Integer. On aura le code suivant:
public class ComparerDesEntiers {
public static void main(String[] args) {
// On effectue 4 comparaisons avec des entiers construits 4 fois de manières différentes
{
Integer b = new Integer(1000);
Integer b2 = new Integer(1000);
if (b==b2) {
System.out.println("b == b2");
}
else {
System.out.println("b <> b2");
}
}
{
Integer b = Integer.valueOf(1000);
Integer b2 = Integer.valueOf(1000);
if (b==b2) {
System.out.println("b == b2");
}
else {
System.out.println("b <> b2");
}
}
{
Integer b = Integer.valueOf("1000");
Integer b2 = Integer.valueOf("1000");
if (b==b2) {
System.out.println("b == b2");
}
else {
System.out.println("b <> b2");
}
}
{
Integer b = 1000;
Integer b2 = 1000;
if (b==b2) {
System.out.println("b == b2");
}
else {
System.out.println("b <> b2");
}
}
}
}
Qu’allons nous avoir à l’exécution ?
Le même résultat que pour la classe Boolean ?
b <> b2
b == b2
b == b2
b == b2
Non. Et la raison en est toute simple. C’est qu’on ne pouvait pas imaginer de mettre en cache un objet par nombre entier. Pour la classe Boolean, c’était facile, il n’y avait que 2 valeurs. Mais pour la classe Integer, il y a 2 exposants 32 valeurs possibles. Cela aurait fait 2 exposants 32 objets à mettre en cache. Cela n’était pas pensable. Et encore moins pour les Long, Float et/ou les Double.
Le résultat de l’exécution est donc:
b <> b2
b <> b2
b <> b2
b <> b2
Mais les développeurs de Sun ne se sont pas arrêtés là. Non, ils ont tout de même décider de mettre en cache, pour les classes enveloppes représentant des nombres entiers, les classes correspondantes aux nombre compris entre -128 et 127. Excepté pour la classe Character, qui ne peut contenir que des nombres entiers non signés, où la cache va de 0 à 127.
Si vous reprenez le code précédent concernant l’Integer, mais en utilisant la valeur 100 au lieu de 1000, le résultat de l’exécution sera donc:
b <> b2
b == b2
b <> b2
b == b2
Tiens. Pourquoi avons-nous b<>b2
comme résultat à la console pour
Integer b = Integer.valueOf("1000");
Integer b2 = Integer.valueOf("1000");
if (b==b2) {
System.out.println("b == b2");
}
else {
System.out.println("b <> b2");
}
Tout simplement, parce qu’il fallait qu’un programme écrit et compilé pour le JDK 1.4 donne les même résultats lorsqu’on qu’il est exécuté avec une JRE 1.4 qu’avec une JRE 5.0. Et la méthode valueOf(String value) existait déjà dans les versions précédentes du JDK 5.0 pour la classe Integer. Et retournait toujours un nouvel objet.
6 Commentaires + Ajouter un commentaire
Commentaires récents
Archives
- janvier 2012
- novembre 2010
- février 2009
- janvier 2009
- décembre 2008
- septembre 2008
- août 2008
- décembre 2007
- octobre 2007
- septembre 2007
- juillet 2007
- mai 2007
- avril 2007
- mars 2007
- février 2007
- janvier 2007
- décembre 2006
- novembre 2006
- octobre 2006
- septembre 2006
- août 2006
- juillet 2006
- juin 2006
- mai 2006
- avril 2006
- février 2006
- janvier 2006
- décembre 2005
- novembre 2005
- octobre 2005
- septembre 2005
- août 2005
- juillet 2005
- juin 2005
- mai 2005
- avril 2005
Catégories
- Certification
- Défis
- Devoxx
- Devoxx 2008
- Devoxx 2010
- Devoxx France 2012
- Divers
- Événements Java
- Fiches
- Hardware
- In English
- Java
- JavaDay 2006
- JavaFX
- JavaOne 2005
- JavaOne 2006
- JavaOne 2007
- Javapolis 2005
- Javapolis 2006
- Javapolis 2007
- JBoss
- Livres
- Mac
- NetBeans
- OpenJDK
- Pensée
- Performance
- Perles
- Sun Tech Days Paris 2007
- Traduction
très intéressant.
En lisant tout cela j’ai testé :
Integer a = 5;
int b = 5;
double c =5;
System.out.println(a==b);
System.out.println(b==a);
System.out.println(a==c);
System.out.println(c==a);
pour information cela renvoie vrai a chaque fois, de l’unboxing donc en cas de comparaison entre primitives et objets
merci beaucoup !!!
Très bonne fiche :). Avant de lire cette fiche, je n’y entendais rien en autoboxing. Maintenant c’est beaucoup plus clair. A manipuler avec beaucoup de précautions tout de même. L’exemple du lock est édifiant : l’autoboxing est dangereux si on ne sait pas parfaitement le manipuler.
Je sens que je vais me la relire une dizaine de fois à chaque auto ou unboxing que je ferai dans un programme :). Merci !
Oui je n’avais pas pensé à ce problème… en général j’utilise un simple Object comme lock.
a++
== et equals() fera le sujet d’une autre fiche. Et donc cela ne devrait pas être un problème.
Par contre, pour revenir au fait que cela ne devrait pas être un réel problème, j’aimerais raconter l’histoire suivante:
Des classes qui devaient supporter le multithreading. Et on crée un object lock, sur lequel on fait un synchronize, pour certaines méthodes. Et on crée un autre objet lock sur lequel on fait un synchronize, pour d’autres méthodes.
Les objets de locks étaient définis comme suit:
Byte lock1 = new Byte(0);
Byte lock2 = new Byte(0);
Et furent ensuite transformés, lors du passage vers JDK 5 en
Byte lock1 = 0;
Byte lock2 = 0;
Les deux locks pointaient maintenant vers les mêmes objets. Et donc le synchronize ne se passait plus du tout comme précédemment, et chute des performances, …
Bref, toujours faire attention.
Très intérréssant et complet…
Mais tu aurais du insister sur le fait que l’opérateur == s’effectue sur les références et que généralement on ne compare pas les références mais les valeurs avec .equals()
Donc ce ne devrait pas être un réel problème…
a++