2010-04-08 17 views
11

Autoboxing è piuttosto spaventoso. Pur comprendendo pienamente la differenza tra == e .equals non posso che aiutare hanno follow bug l'inferno fuori di me:Perché il compilatore/JVM non può semplicemente rendere automatico il boxing "funziona"?

final List<Integer> foo = Arrays.asList(1, 1000); 
    final List<Integer> bar = Arrays.asList(1, 1000); 
    System.out.println(foo.get(0) == bar.get(0)); 
    System.out.println(foo.get(1) == bar.get(1)); 

che stampa

true 
false 

Perché l'hanno fatto in questo modo? Ha qualcosa a che fare con gli Interi memorizzati nella cache, ma se questo è il caso, perché non si limitano a memorizzare tutti gli Integi usati dal programma? O perché la JVM non si disattiva sempre automaticamente alla primitiva?

La stampa false false o true true sarebbe andata molto meglio.

EDIT

Non sono d'accordo per rottura del vecchio codice. Avendo restituito il valore foo.get(0) == bar.get(0), hai già infranto il codice.

non può risolvere il problema a livello di compilatore sostituendo numeri interi con int in bytecode (fintanto che non viene mai assegnato null)

+8

funziona! non il modo in cui ti aspettavi, però;) – OscarRyz

+0

In realtà il tuo esempio ha poco a che fare con l'autoboxing, questo comportamento lo precede. È vero che l'autoboxing ci costringe ad essere più consapevoli di esso: equals() è il modo (di solito corretto) di confrontare gli oggetti, == è il (solo) modo di confrontare le primitive ... autoboxing aiuta il programmatore a trattare Integer e int (quasi) in modo intercambiabile ... e quindi il pericolo di insetti qui. – leonbloy

+0

Ciò può essere dovuto a particolarità che riguardano anche le stringhe. Per quanto comprendo le cose, c'è un po 'di condivisione che si svolge dietro le quinte in base al valore.Questo può essere evitato usando la parola chiave 'new'. –

risposta

9
  • Perché l'hanno fatto in questo modo?

Ogni numero intero compreso tra -128 e 127 viene memorizzato nella cache da java. Lo hanno fatto, presumibilmente, per il beneficio delle prestazioni. Anche se volessero tornare su questa decisione ora, è improbabile che lo facciano. Se qualcuno ha creato un codice in base a questo, il suo codice si interromperebbe quando è stato rimosso. Per la codifica hobby, questo forse non importa, ma per il codice aziendale, le persone si arrabbiano e le cause si verificano.

  • Perché non memorizzano solo tutti gli interi utilizzati dal programma?

Tutti gli integer non possono essere memorizzati nella cache, poiché le implicazioni della memoria sarebbero enormi.

  • Perché la JVM non si disattiva sempre automaticamente alla primitiva?

Perché la JVM non può sapere cosa volevi. Inoltre, questa modifica potrebbe facilmente rompere il codice legacy non creato per gestire questo caso.

Se JVM viene automaticamente rimosso dalle primitive per le chiamate su ==, questo problema diventerà in realtà PIÙ confuso. Ora è necessario ricordare che == confronta sempre i riferimenti agli oggetti, a meno che gli oggetti non possano essere unbox. Ciò causerebbe ancora casi di confusione più strani, proprio come quello che hai affermato sopra.

Invece di preoccuparsi troppo duro su questo, basta ricordare questa regola invece:

MAI confrontare gli oggetti con == a meno che non si ha intenzione di essere confrontandoli con i loro riferimenti. Se lo fai, non riesco a pensare a uno scenario in cui ti imbatti in un problema.

+0

Mai dire ** Mai **. Dovresti ** confrontare i valori di 'enum' con ==. –

+0

@Alexander Pogrebnyak - Avete ragione, ed è per questo che ho aggiunto la clausola "a meno che non intendiate confrontarli con i loro riferimenti". Questo copre le enumerazioni. Sto al mio ** mai ** :) –

+2

Punto giusto. Allora hai ancora la tua "Licenza per uccidere -9" :). 007 out –

7

Potete immaginare come prestazioni male sarebbe se ogni Integer portato in testa per internamento? Inoltre non funziona per new Integer.

Il linguaggio Java (non un problema JVM) non può sempre annullare l'invio automatico perché il codice progettato per Java precedente 1.5 deve ancora funzionare.

+0

buona menzione del codice pre-1.5. +1 – Bozho

+0

un altro +1 per aver menzionato il problema di compatibilità all'indietro – Chris

+0

hanno già infranto il vecchio codice memorizzando la cache da -128 a 127 – Pyrolistical

5

Integer s nell'intervallo di byte sono lo stesso oggetto, perché sono memorizzati nella cache. Integer s al di fuori dell'intervallo di byte non lo sono. Se tutti gli interi dovessero essere memorizzati nella cache, immagina la memoria richiesta.

E da here

Il risultato di tutta questa magia è che si può in gran parte ignorare la distinzione tra int e Integer, con alcune precisazioni. Un'espressione Integer può avere un valore nullo. Se il tuo programma tenta di autounbox null, genererà una NullPointerException. L'operatore == esegue confronti di identità di riferimento su espressioni Integer e confronti di uguaglianza di valori su espressioni int. Infine, ci sono i costi di prestazioni associati con la boxe e unboxing, anche se viene fatto automaticamente

+0

"Numero intero esterno a [-128, 127] può essere o non essere internato. (E penso che la domanda originale capisca cosa sta succedendo, ma vuole sapere perché?) –

+0

Penso che non siano con le implementazioni del sole. Ad ogni modo, si tratta di memorizzarli nella cache e delle risorse necessarie per il caching. – Bozho

4

Se si ignora completamente il boxing automatico, si ottiene comunque questo comportamento.

final List<Integer> foo = 
    Arrays.asList(Integer.valueOf(1), Integer.valueOf(1000)); 
final List<Integer> bar = 
    Arrays.asList(Integer.valueOf(1), Integer.valueOf(1000)); 

System.out.println(foo.get(0) == bar.get(0)); // true 
System.out.println(foo.get(1) == bar.get(1)); // false 

essere più espliciti, se si desidera un comportamento specifico:

final List<Integer> foo = 
    Arrays.asList(new Integer(1), new Integer(1000)); 
final List<Integer> bar = 
    Arrays.asList(new Integer(1), new Integer(1000)); 

System.out.println(foo.get(0) == bar.get(0)); // false 
System.out.println(foo.get(1) == bar.get(1)); // false 

Questa è una ragione, perché Eclipse ha autoboxing come un avvertimento per impostazione predefinita.

+0

Non è attivo per impostazione predefinita nella mia copia di Eclipse e non l'ho modificato. Che versione stai usando? Ho controllato 3.2 e 3.4. – Chris

+0

@Crhis: Eclipse Gallileo (3.5) Finestra-> Preferenze Java-> Compilatore-> Errori/Avvisi-> Potenziali problemi di programmazione-> Conversioni Boxing e Unboxing. Forse non è ON di default, ma l'ho acceso da quando sono passato a Java 5. –

+0

Non è sicuramente attivo per impostazione predefinita. L'ho appena cambiato quando l'ho letto qui. –

3

Un sacco di persone hanno problemi con questo problema, anche con persone che scrivono libri su Java.

In Pro Java Programming, pochi pollici sotto sono stati l'autore parla di problemi con l'uso di auto-boxed Integers come chiave in una IdentityHashMap, egli utilizza chiavi Integer auto-box in una WeakHashMap. I valori di esempio che utilizza sono maggiori di 128, quindi la sua chiamata di garbage collection ha esito positivo. Se qualcuno dovesse usare il suo esempio e usare valori minori di 128, il suo esempio fallirebbe (a causa della chiave perma-cache).

2

Quando si scrive

foo.get(0) 

il compilatore non importa come si è creato l'elenco. Guarda solo il tipo in fase di compilazione della lista foo. Quindi, se questo è un elenco < intero, lo tratterà come un elenco < intero, come si suppone debba fare e un elenco < intero restituisce sempre un intero. Se si desidera utilizzare il == allora dovete scrivere

System.out.println(foo.get(0).intValue() == bar.get(0).intValue()); 

non

System.out.println(foo.get(0) == bar.get(0)); 

perché questo ha un significato completamente diverso.

Problemi correlati