Boxe Integer bizarre à Java

114

Je viens de voir du code similaire à celui-ci:

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

Lorsqu'il est exécuté, ce bloc de code imprimera:

false
true

Je comprends pourquoi le premier est false: parce que les deux objets sont des objets séparés, donc le ==compare les références. Mais je ne peux pas comprendre, pourquoi la deuxième déclaration revient-elle true? Existe-t-il une étrange règle de mise en boîte automatique qui intervient lorsque la valeur d'un entier est dans une certaine plage? Que se passe t-il ici?

Joël
la source
1
On dirait une dupe de stackoverflow.com/questions/1514910/…
3
@RC - Pas tout à fait une dupe, mais une situation similaire est discutée. Merci pour la référence cependant.
Joel
2
c'est horrible. c'est pourquoi je n'ai jamais compris le point de tout ce primitif, mais objet, mais les deux, mais auto-boxé, mais dépend, mais aaaaaaaaargh.
njzk2
1
@Razib: Le mot "autoboxing" n'est pas du code, alors ne le formatez pas comme ça.
Tom

Réponses:

102

La trueligne est en fait garantie par la spécification du langage. De la section 5.1.7 :

Si la valeur p étant encadrée est true, false, un octet, un caractère dans la plage \ u0000 à \ u007f, ou un entier ou un nombre court entre -128 et 127, alors laissez r1 et r2 les résultats de deux conversions de boxe de p. C'est toujours le cas que r1 == r2.

La discussion se poursuit, 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, encadrer une valeur primitive p donnée donnerait toujours une référence identique. En pratique, cela peut ne pas être possible en utilisant les techniques de mise en œuvre existantes. Les règles ci-dessus sont un compromis pragmatique. La dernière clause ci-dessus exige que certaines valeurs communes soient toujours encadrées dans des objets indiscernables. L'implémentation peut les mettre en cache, paresseusement ou avec empressement.

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

Cela garantit que dans la plupart des cas courants, le comportement sera celui souhaité, sans imposer une pénalité de performance excessive, en particulier sur les petits appareils. Des implémentations moins limitées en mémoire peuvent, par exemple, mettre en cache tous les caractères et courts métrages, ainsi que les entiers et longs dans la plage de -32K à + 32K.

Jon Skeet
la source
17
Il peut également être intéressant de noter que l'autoboxing n'est en fait que du sucre syntaxique pour appeler la valueOfméthode de la classe box (comme Integer.valueOf(int)). Il est intéressant de noter que le JLS définit le désugarage exact de déballage - en utilisant intValue()et al - mais pas le désuétisme de boxe.
gustafc
@gustafc il n'y a pas d'autre moyen de déballer un Integerque via l' publicAPI officielle , c'est-à-dire en appelant intValue(). Mais il existe d'autres moyens possibles d'obtenir une Integerinstance pour une intvaleur, par exemple un compilateur peut générer du code en conservant et en réutilisant des Integerinstances précédemment créées .
Holger
31
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);
   }
}

Production:

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, ce qui est similaire à -

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

La deuxième sortie est produite parce que le JVMessaie d'économiser de la mémoire, lorsque le Integertombe 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 créé précédemment 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 instances des objets wrapper suivants (lors de leur création par boxing), seront toujours == où leurs valeurs primitives sont les mêmes -

  • Booléen
  • Octet
  • Caractère de \ u0000 à \u007f(7f est 127 en décimal)
  • Court et entier de -128 à 127
Razib
la source
2
Longa également un cache avec la même plage que Integer.
Eric Wang
8

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

Adam Crume
la source
1
Cette plage peut être étendue à l'aide de java.lang.Integer.IntegerCache.highproperty. Intéressant que Long n'ait pas cette option.
Aleksandr Kravets
5

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

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

Avi
la source
4

Je suppose que Java garde un cache de petits entiers qui sont déjà `` encadrés '' parce qu'ils sont très courants et que cela permet de gagner beaucoup de temps pour réutiliser un objet existant plutôt que pour en créer un nouveau.

Très varié
la source
4

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é de 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));
    }
}

Retour:

true
true
AmirHd
la source
@Joel a demandé un tout autre sujet, pas l'égalité d'entiers mais le comportement d'exécution des objets.
Iliya Kuznetsov
3

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 égal.

Marvin
la source
3

L'affectation directe d'un littéral int à une référence Integer est un exemple d'auto-boxing, 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 se convertit Integer a = 1000, b = 1000;en Integer a = Integer.valueOf(1000), b = Integer.valueOf(1000);.

C'est donc la Integer.valueOf()méthode qui nous donne en fait les objets entiers, et si nous regardons le code source de la Integer.valueOf()méthode, nous pouvons clairement voir que la méthode met en cache les objets entiers dans la plage -128 à 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, Integer.valueOf()la méthode renvoie des objets Integer à partir de l'interne IntegerCachesi le littéral int passé 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é à 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' -XX:AutoBoxCacheMaxoption JVM.

Ce comportement de mise en cache n'est pas applicable uniquement aux objets Integer, similaire à Integer.IntegerCache que nous avons également ByteCache, ShortCache, LongCache, CharacterCachepour Byte, Short, Long, Characterrespectivement.

Vous pouvez en savoir plus sur mon article Java Integer Cache - Why Integer.valueOf (127) == Integer.valueOf (127) Is True .

Naresh Joshi
la source
0

Dans Java 5, une nouvelle fonctionnalité a été introduite pour économiser la mémoire et améliorer les performances des manipulations d'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. Cela s'applique aux valeurs entières comprises entre –127 et +127 (valeur entière max).

  2. Cette mise en cache Integer ne fonctionne que sur l'auto-box. Les objets entiers ne seront pas mis en cache lorsqu'ils sont construits à l'aide du constructeur.

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

Cache d'entiers en détail

Rahul Maurya
la source
0

Si nous vérifions le code source de Integerobeject, nous trouverons la source de la valueOfméthode 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);
}

ce qui peut expliquer pourquoi les Integerobjets, qui dans la plage de -128 ( Integer.low) à 127 ( Integer.high), sont les mêmes objets référencés pendant l'autoboxing. Et nous pouvons voir qu'une classe IntegerCaches'occupe du Integertableau de cache, qui est une classe interne statique privée de la Integerclasse.

Il y a un autre exemple intéressant qui peut 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    

}
L Joey
la source