web-dev-qa-db-fra.com

Bizarre boxe Integer en Java

Je viens de voir un code similaire à ceci:

public class Scratch
{
    public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;
        System.out.println(a == b);

        Integer c = 100, d = 100;
        System.out.println(c == d);
    }
}

Une fois lancé, ce bloc de code affichera:

false
true

Je comprends pourquoi le premier est false: parce que les deux objets sont des objets distincts, le == compare les références. Mais je n'arrive pas à comprendre pourquoi la deuxième déclaration renvoie true? Existe-t-il une étrange règle de substitution automatique qui intervient lorsque la valeur d'un entier est comprise dans une certaine plage? Que se passe t-il ici?

102
Joel

La ligne true est en réalité garantie par la spécification de langue. De section 5.1.7 :

Si la valeur p qui est encadrée est vraie, false, un octet, un caractère dans la plage \u0000 à\u007f, ou un int ou court nombre compris entre -128 et 127, puis laissez r1 et r2 sont les résultats de deux quelconques conversions de boxe de p. C'est toujours le cas où r1 == r2.

La discussion continue, suggérant que bien que votre deuxième ligne de sortie soit garantie, la première ne l’est pas (voir le dernier paragraphe cité ci-dessous):

Idéalement, boxer une primitive donnée la valeur p produirait toujours un référence identique. En pratique, cela peut ne pas être faisable en utilisant existant techniques de mise en œuvre. Les règles ci-dessus sont un compromis pragmatique. Le La clause finale ci-dessus exige que certaines valeurs communes doivent toujours être encadrées en objets impossibles à distinguer. Le l'implémentation peut les mettre en cache, paresseusement ou avidement.

Pour d'autres valeurs, cette formulation rejette toute hypothèse sur le identité des valeurs encadrées sur le la partie du programmeur. Cela permettrait (mais ne nécessite pas) le partage de certains ou toutes ces références.

Cela garantit que dans la plupart des communes cas, le comportement sera le celui désiré, sans imposer une indue pénalité de performance, en particulier sur petits appareils. Moins de mémoire limitée les implémentations peuvent, par exemple, cache tous les caractères et shorts, comme ainsi que des nombres entiers et longs dans le plage de -32K - + 32K.

96
Jon Skeet
public class Scratch
{
   public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;  //1
        System.out.println(a == b);

        Integer c = 100, d = 100;  //2
        System.out.println(c == d);
   }
}

Sortie:

false
true

Oui, la première sortie est produite pour comparer la référence; 'a' et 'b' - ce sont deux références différentes. Au point 1, en fait, deux références sont créées, qui sont similaires à - 

Integer a = new Integer(1000);
Integer b = new Integer(1000);

La deuxième sortie est générée car la JVM tente d'économiser de la mémoire lorsque la Integer tombe dans une plage (de -128 à 127). Au point 2, aucune nouvelle référence de type Integer n'est créée pour 'd'. Au lieu de créer un nouvel objet pour la variable de référence de type Integer 'd', il n'est affecté qu'à l'objet précédemment créé, référencé par 'c'. Tout cela est fait par JVM

Ces règles d'économie de mémoire ne s'appliquent pas uniquement à Integer. à des fins d’économie de mémoire, deux occurrences des objets wrapper suivants (lorsqu’elles sont créées par le biais de la boxe) seront toujours == où leurs valeurs primitives sont identiques 

  • Booléen
  • Octet
  • Caractère de\u0000 à \u007f (7f est 127 en décimal) 
  • Short et Integer de -128 à 127
24
Razib

Les objets entiers dans une certaine plage (peut-être entre -128 et 127) sont mis en cache et réutilisés Les entiers en dehors de cette plage reçoivent un nouvel objet à chaque fois.

8
Adam Crume

C'est un point intéressant. Dans le livre Effective Java suggère de toujours remplacer les égaux pour vos propres classes. De plus, pour vérifier l'égalité pour deux instances d'objet d'une classe Java, utilisez toujours la méthode equals. 

public class Scratch
{
    public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;
        System.out.println(a.equals(b));

        Integer c = 100, d = 100;
        System.out.println(c.equals(d));
    }
}

résultats:

true
true
4
AmirHd

Oui, il existe une étrange règle de substitution automatique qui intervient lorsque les valeurs se situent dans une certaine plage. Lorsque vous affectez une constante à une variable Object, rien dans la définition de langage n'indique qu'un nouvel objet doit soit créé. Il peut réutiliser un objet existant du cache.

En fait, la machine virtuelle Java stocke généralement un cache de petits entiers à cette fin, ainsi que des valeurs telles que Boolean.TRUE et Boolean.FALSE.

4
Avi

En Java, la boxe fonctionne dans la plage comprise entre -128 et 127 pour un entier. Lorsque vous utilisez des nombres dans cette plage, vous pouvez le comparer avec l'opérateur ==. Pour les objets Integer en dehors de la plage, vous devez utiliser égaux.

3
marvin

J’imagine que Java conserve un cache de petits entiers qui sont déjà «encadrés» car ils sont très courants et cela permet de gagner beaucoup de temps pour réutiliser un objet existant plutôt que pour en créer un nouveau.

3
Omnifarious

L'attribution directe d'un littéral int à une référence Integer est un exemple de boxe automatique, où la valeur littérale en code de conversion d'objet est gérée par le compilateur.

Ainsi, pendant la phase de compilation, le compilateur convertit Integer a = 1000, b = 1000; en Integer a = Integer.valueOf(1000), b = Integer.valueOf(1000);.

C'est donc la méthode Integer.valueOf() qui nous donne les objets entiers, et si nous examinons le code source de la méthode Integer.valueOf(), nous pouvons clairement voir que la méthode met en cache les objets entiers compris entre -128 et 127 (inclus).

/**
 *
 * 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);
 }

Ainsi, au lieu de créer et de renvoyer de nouveaux objets entiers, la méthode Integer.valueOf() renvoie les objets Integer à partir de la variable interne IntegerCache si le littéral int transmis est supérieur à -128 et inférieur à 127.

Java met en cache ces objets entiers car cette plage d'entiers est beaucoup utilisée dans la programmation quotidienne, ce qui économise indirectement de la mémoire.

Le cache est initialisé lors de la première utilisation lorsque la classe est chargée en mémoire à cause du bloc statique. La plage maximale du cache peut être contrôlée par l’option -XX:AutoBoxCacheMax JVM.

Ce comportement de mise en cache ne s'applique pas aux objets Integer uniquement. De manière similaire à Integer.IntegerCache, nous avons également ByteCache, ShortCache, LongCache, CharacterCache pour Byte, Short, Long, Character respectivement.

Vous pouvez en lire plus sur mon article Cache Java Integer - Pourquoi Integer.valueOf (127) == Integer.valueOf (127) est vrai .

1
Naresh Joshi

En Java 5, une nouvelle fonctionnalité a été introduite pour économiser la mémoire et améliorer les performances pour la gestion des objets de type Integer. Les objets entiers sont mis en cache en interne et réutilisés via les mêmes objets référencés.

  1. Ceci s'applique aux valeurs entières comprises entre –127 et +127 (Valeur de nombre entier max.).

  2. Cette mise en cache Integer ne fonctionne que sur la sélection automatique. Les objets entiers ne seront pas mis en cache lorsqu'ils seront construits à l'aide du constructeur.

Pour plus de détails, veuillez consulter le lien ci-dessous:

Cache de nombre entier en détail

0
Rahul Maurya

Si nous vérifions le code source de Integer obeject, nous trouverons la source de la méthode valueOf comme ceci: 

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

cela peut expliquer pourquoi les objets Integer qui, dans la plage allant de -128 (Integer.low) à 127 (Integer.high), sont les mêmes objets référencés lors de la sélection automatique Et nous pouvons voir qu'il existe une classe IntegerCache prend en charge le tableau de cache Integer, qui est une classe interne statique privée de la classe Integer.

Il existe un autre exemple intéressant qui pourrait nous aider à comprendre cette situation étrange:

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

      Class cache = Integer.class.getDeclaredClasses()[0]; 
      Field myCache = cache.getDeclaredField("cache"); 
      myCache.setAccessible(true);

      Integer[] newCache = (Integer[]) myCache.get(cache); 
      newCache[132] = newCache[133]; 

      Integer a = 2;
      Integer b = a + a;
      System.out.printf("%d + %d = %d", a, a, b); //The output is: 2 + 2 = 5    

}
0
L Joey