Exercice : Comment faire 1 + 2 = 4 en Java ?

java_logo

Dans cet article, je vous propose un petit exercice qui me permettra de vous illustrer quelques mécanismes du langage Java.

Soit le bloc de code suivant :

Integer a = 1;
Integer b = 2;
System.out.println(a + b); // résultat : 3
1 + 2 = 3

 

Comment faire pour afficher "4" sans toucher à ces trois lignes ?

 

Voici un indice. Pour répondre à cette question, nous avons besoin de ces 3 notions :

  • Autoboxing
  • Réflexion
  • Cache

 

Réponse

Integer c = 2;
Field field = c.getClass().getDeclaredField("value");
field.setAccessible(true);
field.setInt(c, 3);

Integer a = 1;
Integer b = 2;
System.out.println(a + b); // résultat : 4
1 + 2 = 4

 

Explications

Vous avez sans doute remarqué que nous manipulons des Integer et non des int.

Lorsque nous faisons "Integer a = 1", le nombre 1, de type int, est transformé en Integer afin d'être affecté à la variable a. Pour ce faire, le mécanisme "d'autoboxing" est appliqué. En réalité, le code suivant est exécuté :

Integer a = Integer.valueOf(1);
autoboxing

 

Voici le le code de la méthode Integer.valueOf(int i)  :

/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value.  If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param  i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since  1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
    return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
Integer.valueOf(int i)

On y découvre que les Integer entre -128 et 127 retournés par la méthode valueOf, et donc par le mécanisme d'autoboxing, sont des objets stockés dans un cache static de la classe Integer. On a donc les résultats suivants :

Integer a = 100, b = 100;  
System.out.println(a == b); // true

Integer c = 1000, d = 1000;  
System.out.println(c == d); // false
Cache d'Integer

 

Si on reprend la réponse de notre exercice :

Integer c = 2;
Field field = c.getClass().getDeclaredField("value");
field.setAccessible(true);
field.setInt(c, 3);

Integer a = 1;
Integer b = 2;
System.out.println(a + b); // résultat : 4
1 + 2 = 4

 

Les variables b et c pointent donc vers la même instance d'Integer. Là est la clef du problème !

Enfin, que font les lignes suivantes ?

Integer c = 2;
Field field = c.getClass().getDeclaredField("value");
field.setAccessible(true);
field.setInt(c, 3);
Réflexion

Ces lignes modifient, par réflexion, l'Integer 2 en remplaçant sa valeur 2 (attribut "value") par le nombre 3.
Remarque : l'appel de la méthode setAccessible(true) est nécessaire car l'attribut "value" est privé.

 

En résumé :

  1. On récupère, dans le cache de la classe Integer, l'instance qui a la valeur 2 et on l'affecte à la variable c.
  2. On lui donne la valeur 3.
  3. On récupère, dans le cache de la classe Integer, l'instance qui a la valeur 1 et on l'affecte à la variable a.
  4. On récupère, dans le cache de la classe Integer, l'instance qui est censée avoir la valeur 2 mais qui vaut en réalité 3, et on l'affecte à la variable b.
  5. On additionne a et b, c'est-à-dire que l'on additionne 1 et 3, ce qui nous donne bien 4.

CQFD !

Sources

3 commentaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.