Strano boxe intero in Java


114

Ho appena visto un codice simile a questo:

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

Quando viene eseguito, questo blocco di codice verrà stampato:

false
true

Capisco perché il primo è false: perché i due oggetti sono oggetti separati, quindi ==confronta i riferimenti. Ma non riesco a capire, perché la seconda affermazione sta tornando true? C'è qualche strana regola di autoboxing che entra in gioco quando il valore di un intero è in un certo intervallo? Cosa sta succedendo qui?


1
Si presenta come una vittima di stackoverflow.com/questions/1514910/...

3
@RC - Non proprio uno stupido, ma viene discussa una situazione simile. Grazie per il riferimento però.
Joel

2
questo è orribile. questo è il motivo per cui non ho mai capito il punto di quell'intero primitivo, ma oggetto, ma entrambi, ma auto-inscatolati, ma dipende, ma aaaaaaaaargh.
njzk2

1
@Razib: La parola "autoboxing" non è un codice, quindi non formattarlo in questo modo.
Tom

Risposte:


102

La truelinea è effettivamente garantita dalla specifica del linguaggio. Dalla sezione 5.1.7 :

Se il valore p boxed è vero, falso, un byte, un carattere nell'intervallo da \ u0000 a \ u007f o un numero int o breve compreso tra -128 e 127, allora siano r1 e r2 il risultato di due conversioni di boxe qualsiasi di p. È sempre vero che r1 == r2.

La discussione continua, suggerendo che sebbene la tua seconda riga di output sia garantita, la prima non lo è (vedi l'ultimo paragrafo citato di seguito):

Idealmente, inscatolare un dato valore primitivo p, produrrebbe sempre un riferimento identico. In pratica, ciò potrebbe non essere fattibile utilizzando le tecniche di implementazione esistenti. Le regole di cui sopra sono un compromesso pragmatico. La clausola finale di cui sopra richiede che alcuni valori comuni siano sempre racchiusi in oggetti indistinguibili. L'implementazione può memorizzarli nella cache, pigramente o avidamente.

Per altri valori, questa formulazione non consente alcuna ipotesi sull'identità dei valori boxed da parte del programmatore. Ciò consentirebbe (ma non richiederebbe) la condivisione di alcuni o tutti questi riferimenti.

Ciò garantisce che nella maggior parte dei casi il comportamento sia quello desiderato, senza imporre un'indebita penalizzazione delle prestazioni, soprattutto su piccoli dispositivi. Ad esempio, implementazioni con meno limitazioni di memoria potrebbero memorizzare nella cache tutti i caratteri e gli short, nonché interi e long nell'intervallo -32K - + 32K.


17
Potrebbe anche valere la pena notare che l'autoboxing è in realtà solo zucchero sintattico per chiamare il valueOfmetodo della classe box (come Integer.valueOf(int)). È interessante che il JLS definisca l'esatto desugaring unboxing - usando intValue()et al - ma non il desugaring boxing.
gustafc

@gustafc non c'è altro modo per unboxing Integerche tramite l' publicAPI ufficiale , cioè chiamando intValue(). Ma ci sono altri modi possibili per ottenere Integerun'istanza per un intvalore, ad esempio un compilatore può generare codice mantenendo e riutilizzando Integeristanze create in precedenza .
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);
   }
}

Produzione:

false
true

Sì, il primo output viene prodotto per il confronto del riferimento; 'a' e 'b' - questi sono due riferimenti diversi. Al punto 1, in realtà vengono creati due riferimenti che sono simili a:

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

Il secondo output viene prodotto perché JVMcerca di salvare memoria, quando Integerrientra in un intervallo (da -128 a 127). Al punto 2 non viene creato nessun nuovo riferimento di tipo Integer per 'd'. Invece di creare un nuovo oggetto per la variabile di riferimento di tipo Integer "d", viene assegnato solo con un oggetto creato in precedenza a cui fa riferimento "c". Tutto questo viene fatto da JVM.

Queste regole di salvataggio della memoria non sono solo per Integer. ai fini del risparmio di memoria, due istanze dei seguenti oggetti wrapper (mentre sono stati creati tramite boxing), saranno sempre == dove i loro valori primitivi sono gli stessi -

  • booleano
  • Byte
  • Carattere da \ u0000 a \u007f(7f è 127 in decimale)
  • Breve e intero da -128 a 127

2
Longha anche una cache con lo stesso intervallo di Integer.
Eric Wang

8

Gli oggetti interi in un certo intervallo (penso che forse da -128 a 127) vengono memorizzati nella cache e riutilizzati. I numeri interi al di fuori di tale intervallo ricevono ogni volta un nuovo oggetto.


1
Questo intervallo può essere esteso utilizzando la java.lang.Integer.IntegerCache.highproprietà. È interessante che Long non abbia questa opzione.
Aleksandr Kravets

5

Sì, c'è una strana regola di autoboxing che entra in gioco quando i valori sono in un certo intervallo. Quando si assegna una costante a una variabile Object, nulla nella definizione del linguaggio dice che è necessario creare un nuovo oggetto . Può riutilizzare un oggetto esistente dalla cache.

In effetti, la JVM di solito memorizza una cache di piccoli numeri interi per questo scopo, oltre a valori come Boolean.TRUE e Boolean.FALSE.


4

La mia ipotesi è che Java mantenga una cache di piccoli numeri interi che sono già "inscatolati" perché sono molto comuni e consente di risparmiare molto tempo per riutilizzare un oggetto esistente piuttosto che crearne uno nuovo.


4

Questo è un argomento interessante. Nel libro Java efficace suggerisce sempre di sovrascrivere uguali per le proprie classi. Inoltre, per verificare l'uguaglianza per due istanze di oggetti di una classe java, utilizzare sempre il metodo 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));
    }
}

ritorna:

true
true

@ Joel ha chiesto un argomento molto altro, non l'uguaglianza di interi ma il comportamento in fase di esecuzione degli oggetti.
Iliya Kuznetsov

3

In Java la boxe funziona nell'intervallo tra -128 e 127 per un numero intero. Quando si utilizzano numeri in questo intervallo, è possibile confrontarli con l'operatore ==. Per gli oggetti Integer al di fuori dell'intervallo devi usare uguale.


3

L'assegnazione diretta di un valore letterale int a un riferimento intero è un esempio di boxing automatico, in cui il valore letterale del codice di conversione dell'oggetto viene gestito dal compilatore.

Quindi durante la fase di compilazione il compilatore converte Integer a = 1000, b = 1000;in Integer a = Integer.valueOf(1000), b = Integer.valueOf(1000);.

Quindi è il Integer.valueOf()metodo che effettivamente ci fornisce gli oggetti interi, e se guardiamo il codice sorgente del Integer.valueOf()metodo possiamo vedere chiaramente che il metodo memorizza nella cache gli oggetti interi nell'intervallo da -128 a 127 (inclusi).

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

Quindi, invece di creare e restituire nuovi oggetti interi, Integer.valueOf()il metodo restituisce oggetti Integer dall'interno IntegerCachese il valore letterale int passato è maggiore di -128 e minore di 127.

Java memorizza nella cache questi oggetti interi perché questo intervallo di numeri interi viene utilizzato molto nella programmazione quotidiana che risparmia indirettamente un po 'di memoria.

La cache viene inizializzata al primo utilizzo quando la classe viene caricata in memoria a causa del blocco statico. L'intervallo massimo della cache può essere controllato -XX:AutoBoxCacheMaxdall'opzione JVM.

Questo comportamento di memorizzazione nella cache non è applicabile solo agli oggetti Integer, simile a Integer.IntegerCache che abbiamo anche ByteCache, ShortCache, LongCache, CharacterCacheper Byte, Short, Long, Characterrispettivamente.

Puoi leggere di più nel mio articolo Java Integer Cache - Why Integer.valueOf (127) == Integer.valueOf (127) Is True .


0

In Java 5, è stata introdotta una nuova funzionalità per salvare la memoria e migliorare le prestazioni per la gestione degli oggetti di tipo Integer. Gli oggetti interi vengono memorizzati nella cache internamente e riutilizzati tramite gli stessi oggetti di riferimento.

  1. Questo è applicabile per i valori Integer nell'intervallo compreso tra –127 e +127 (valore Integer massimo).

  2. Questa memorizzazione nella cache di numeri interi funziona solo sull'autoboxing. Gli oggetti interi non verranno memorizzati nella cache quando vengono creati utilizzando il costruttore.

Per maggiori dettagli, passare al link sottostante:

Cache intera in dettaglio


0

Se controlliamo il codice sorgente di Integerobeject, troveremo il sorgente del valueOfmetodo proprio in questo modo:

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

che può spiegare perché gli Integeroggetti, che nell'intervallo da -128 ( Integer.low) a 127 ( Integer.high), sono gli stessi oggetti di riferimento durante l'autoboxing. E possiamo vedere che c'è una classe che si IntegerCacheprende cura Integerdell'array della cache, che è una classe interna statica privata della Integerclasse.

C'è un altro esempio interessante che può aiutarci a capire questa strana situazione:

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    

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.