Perché le persone usano ancora tipi primitivi in ​​Java?


163

A partire da Java 5, abbiamo avuto boxe / unboxing di tipi primitivi in ​​modo intda essere racchiusi java.lang.Integer, e così e così via.

Ultimamente vedo molti nuovi progetti Java (che richiedono sicuramente un JRE almeno della versione 5, se non 6) che stanno usando intpiuttosto che java.lang.Integer, sebbene sia molto più conveniente usare quest'ultimo, poiché ha alcuni metodi di aiuto per la conversione ai longvalori et al.

Perché alcuni usano ancora tipi primitivi in ​​Java? C'è qualche beneficio tangibile?


49
hai mai pensato al consumo e alle prestazioni della memoria?
Tedil

76
Ho aggiunto il tag autoboxing ... e ho scoperto che tre persone lo seguono effettivamente. Veramente? Le persone seguono il tag AUTOBOXING?
corsiKa

4
@glowcoder Non sono persone reali, sono solo concetti astratti che assumono una forma umana per rispondere su SO. :)
biziclop

9
@TK Kocheran Principalmente perché new IntegeR(5) == new Integer(5)dovrebbe secondo le regole, valutare falso.
biziclop,

10
Vedi GNU Trove o Mahout Collections o HPPC o ... per soluzioni a raccolte di tipi primitivi. Quelli di noi che si preoccupano della velocità trascorrono il nostro tempo usando tipi più primitivi, non meno.
bmargulies

Risposte:


395

In Effective Java di Joshua Bloch , elemento 5: "Evita di creare oggetti non necessari", pubblica il seguente esempio di codice:

public static void main(String[] args) {
    Long sum = 0L; // uses Long, not long
    for (long i = 0; i <= Integer.MAX_VALUE; i++) {
        sum += i;
    }
    System.out.println(sum);
}

e ci vogliono 43 secondi per l'esecuzione. Portare il Long nella primitiva lo porta a 6,8 secondi ... Se questo è un motivo per cui usiamo le primitive.

Anche la mancanza di uguaglianza di valore nativo è una preoccupazione ( .equals()è abbastanza dettagliata rispetto a== )

per biziclop:

class Biziclop {

    public static void main(String[] args) {
        System.out.println(new Integer(5) == new Integer(5));
        System.out.println(new Integer(500) == new Integer(500));

        System.out.println(Integer.valueOf(5) == Integer.valueOf(5));
        System.out.println(Integer.valueOf(500) == Integer.valueOf(500));
    }
}

Risultati in:

false
false
true
false

EDIT Perché (3) ritorna truee (4) ritorna false?

Perché sono due oggetti diversi. I 256 numeri più vicini allo zero [-128; 127] sono memorizzati nella cache dalla JVM, quindi restituiscono lo stesso oggetto per quelli. Oltre tale intervallo, tuttavia, non vengono memorizzati nella cache, quindi viene creato un nuovo oggetto. Per rendere le cose più complicate, JLS richiede che vengano memorizzati nella cache almeno 256 pesi mosca. Gli implementatori JVM possono aggiungere altro se lo desiderano, il che significa che potrebbe essere eseguito su un sistema in cui sono memorizzati nella cache i 1024 più vicini e tutti restituiscono true ... #awkward


54
Ora immagina se anche tu venissi idichiarato Long!
ColinD

14
@TREE: le specifiche in realtà richiedono alle macchine virtuali di creare pesi mosca entro un certo intervallo. Ma purtroppo consente loro di estendere tale intervallo, il che significa che i programmi possono comportarsi in modo diverso su macchine virtuali diverse. Tanto per cross platform ...
Daniel Earwicker

12
Java è andato a vuoto, con scelte di progettazione sempre più cattive. L'autoboxing è un fallimento completo, non è né robusto, prevedibile o portatile. Mi chiedo davvero cosa stessero pensando ... invece di sistemare la temuta dualità dell'oggetto primitivo che sono riusciti a peggiorare rispetto al primo posto.
Pop Catalin,

34
@Catalin Non sono d'accordo con te sul fatto che l'autoboxing sia un fallimento completo. Ha alcuni difetti, che non è diverso da qualsiasi altro design che avrebbe potuto essere usato (incluso niente.) Rendono molto chiaro ciò che puoi e non puoi aspettarti, e come qualsiasi altro design si aspettano che gli sviluppatori conoscano e obbediscano ai contratti di quei disegni.
corsiKa

9
@NaftuliTzviKay Non è un "fallimento". Rendono MOLTO CHIARO che l' ==operatore esegua confronti di identità di riferimento su Integerespressioni e confronti di uguaglianza di valore su intespressioni. Integer.equals()esiste proprio per questo motivo. Non si dovrebbe mai usare ==per confrontare i valori in qualsiasi tipo non primitivo. Questo è Java 101.
NullUserException

86

L'autounboxing può portare a NPE difficili da individuare

Integer in = null;
...
...
int i = in; // NPE at runtime

Nella maggior parte dei casi l'assegnazione nulla a inè molto meno ovvia di quanto sopra.


43

I tipi in scatola hanno prestazioni inferiori e richiedono più memoria.


40

Tipi primitivi:

int x = 1000;
int y = 1000;

Ora valuta:

x == y

Lo è true. Difficilmente sorprendente. Ora prova i tipi in scatola:

Integer x = 1000;
Integer y = 1000;

Ora valuta:

x == y

Lo è false. Probabilmente. Dipende dal tempo di esecuzione. È sufficiente questa ragione?


36

Oltre ai problemi di prestazioni e di memoria, vorrei trovare un altro problema: l' Listinterfaccia sarebbe stata interrotta senza int.
Il problema è il remove()metodo sovraccarico ( remove(int)vs. remove(Object)). remove(Integer)risolverebbe sempre la chiamata a quest'ultimo, quindi non è possibile rimuovere un elemento per indice.

D'altra parte, c'è un errore nel tentativo di aggiungere e rimuovere un int:

final int i = 42;
final List<Integer> list = new ArrayList<Integer>();
list.add(i); // add(Object)
list.remove(i); // remove(int) - Ouch!

7
Sarebbe rotto, sì. Tuttavia, rimuovere (int) è un difetto di progettazione IMO. I nomi dei metodi non dovrebbero mai essere sovraccaricati se esiste la minima possibilità di confusione.
MrBackend,

4
@MrBackend Abbastanza giusto. È interessante notare che Vectoraveva removeElementAt(int)dall'inizio. remove(int)è stato introdotto con il framework delle raccolte in Java 1.2.
Xehpuk,

6
@MrBackend: quando è Liststata progettata l' API, non esistevano né Generics né Autoboxing, quindi non c'erano possibilità di confusione remove(int)e remove(Object)...
Holger,

@Franklin Yu: certo, ma quando si progetta una nuova lingua / versione senza vincoli di compatibilità, non ci si fermerebbe a cambiare questo sfortunato sovraccarico. Ti sbarazzeresti semplicemente della distinzione di primitivi e valori inscatolati del tutto, in modo che la domanda da utilizzare non appaia mai.
Holger,

27

Riesci davvero a immaginare a

  for (int i=0; i<10000; i++) {
      do something
  }

loop con java.lang.Integer invece? Un java.lang.Integer è immutabile, quindi ogni incremento attorno al ciclo creerebbe un nuovo oggetto java sull'heap, anziché incrementare semplicemente l'int sullo stack con una singola istruzione JVM. La performance sarebbe diabolica.

Non sarei davvero d'accordo sul fatto che sia molto conveniente utilizzare java.lang.Integer rispetto a int. Anzi. Autoboxing significa che è possibile utilizzare int dove altrimenti si sarebbe costretti a utilizzare Integer e il compilatore java si occupa di inserire il codice per creare il nuovo oggetto Integer. L'autoboxing ti consente di utilizzare un int in cui è previsto un numero intero, con il compilatore che inserisce la relativa costruzione dell'oggetto. In primo luogo non rimuove o riduce la necessità di int. Con l'autoboxing ottieni il meglio da entrambi i mondi. Ottieni un intero creato automaticamente per te quando hai bisogno di un oggetto java basato su heap e ottieni la velocità e l'efficienza di un int quando esegui calcoli aritmetici e locali.


19

I tipi primitivi sono molto più veloci:

int i;
i++;

Integer (tutti i numeri e anche una stringa) è un tipo immutabile : una volta creato non può essere modificato. Se ifosse Integer, i++ciò creerebbe un nuovo oggetto Integer - molto più costoso in termini di memoria e processore.


Non vuoi che una variabile cambi se lo fai i++su un'altra variabile, quindi Integer deve essere immutabile per poterlo fare (o almeno questo i++dovrebbe creare comunque un nuovo oggetto Integer). (E anche i valori primitivi sono immutabili - semplicemente non lo noti dal momento che non sono oggetti.)
Paŭlo Ebermann

4
@ Paŭlo: Dire che i valori primitivi sono immutabili è in qualche modo insignificante. Quando riassegni una variabile primitiva a un nuovo valore, non stai creando nulla di nuovo. Nessuna allocazione di memoria è coinvolta. Il punto di Peter è: i ++ per una primitiva non ha allocazione di memoria, ma per un oggetto lo fa necessariamente.
Eddie,

@Eddie: (Non ha necessariamente bisogno di allocazione di memoria, potrebbe anche restituire un valore memorizzato nella cache. Per alcuni piccoli valori, credo.) Il mio punto era che l'immutabilità di Integers qui non è il punto decisivo, che vorresti comunque avere un altro oggetto, indipendentemente dall'immutabilità.
Paŭlo Ebermann,

@ Paŭlo: il mio unico punto era che Integer è un ordine di grandezza più lento dei primitivi. E ciò è dovuto al fatto che i tipi boxed sono immutabili e ogni volta che si modifica un valore viene creato un nuovo oggetto. Non ho affermato che c'è qualcosa di sbagliato in loro o il fatto che siano immutabili. Solo che sono più lenti e che un programmatore dovrebbe saperlo. Dai un'occhiata a come Groovy paga
Peter Knego

1
Immutabilità ed ++è un'aringa rossa qui. Immagina che Java sia stato migliorato per supportare il sovraccarico degli operatori in un modo davvero semplice, in modo tale che se una classe (come ad esempio Integerha un metodo plus, allora potresti scrivere i + 1invece di i.plus(1). E supponi anche che il compilatore sia abbastanza intelligente da espandersi i++in i = i + 1. Ora puoi dire i++ed efficacemente "incrementa la variabile i" senza Integeressere mutabile.
Daniel Earwicker

16

Innanzitutto, abitudine. Se hai codificato in Java per otto anni, accumuli una notevole quantità di inerzia. Perché cambiare se non ci sono motivi validi per farlo? Non è come se l'uso di primitivi inscatolati comporta ulteriori vantaggi.

L'altro motivo è affermare che nullnon è un'opzione valida. Sarebbe inutile e fuorviante dichiarare la somma di due numeri o una variabile di ciclo comeInteger .

C'è anche l'aspetto delle prestazioni, mentre la differenza di prestazioni non è critica in molti casi (anche se quando lo è, è piuttosto male), a nessuno piace scrivere codice che potrebbe essere scritto altrettanto facilmente in un modo più veloce che già siamo abituato a.


15
Non sono d'accordo. L'aspetto prestazionale può essere critico. Molto poco di ciò è probabilmente inerziale o forza dell'abitudine.
Eddie,

7
@Eddie Può essere, ma molto raramente lo è. Fidati di me, per la maggior parte delle persone gli argomenti di performance sono solo una scusa.
biziclop,

3
Anch'io vorrei proteggere l'argomento delle prestazioni. Su Android con Dalvik ogni oggetto che crei aumenterà il "rischio" di chiamata GC e più oggetti avrai le pause saranno più lunghe. Quindi la creazione di numeri interi anziché int in un ciclo probabilmente ti costerà alcuni frame persi.
Igor Čordaš,

1
@PSIXO È giusto, l'ho scritto pensando a Java puramente sul lato server. I dispositivi mobili sono un animale completamente diverso. Ma il mio punto era che anche gli sviluppatori che altrimenti scrivono un codice terribile senza alcun riguardo per le prestazioni lo citeranno come una ragione, da loro sembra molto una scusa.
biziclop,

12

A proposito, Smalltalk ha solo oggetti (senza primitive), eppure avevano ottimizzato i loro piccoli numeri interi (usando non tutti i 32 bit, solo 27 o simili) per non allocare alcun spazio dell'heap, ma semplicemente usando un modello di bit speciale. Anche altri oggetti comuni (vero, falso, nullo) avevano schemi di bit speciali qui.

Quindi, almeno su JVM a 64 bit (con uno spazio dei nomi del puntatore a 64 bit) dovrebbe essere possibile non avere alcun oggetto Integer, Character, Byte, Short, Boolean, Float (e small Long) (a parte questi creati da esplicito new ...()), solo modelli di bit speciali, che potrebbero essere manipolati in modo abbastanza efficiente dai normali operatori.


Avrei dovuto dire "alcune implementazioni", poiché questo non è regolato dalle specifiche del linguaggio, credo. (E purtroppo non posso citare alcuna fonte qui, è solo da quello che ho sentito da qualche parte.)
Paŭlo Ebermann

Inoltre, JIT mantiene già meta nel puntatore; Inoltre, il puntatore può conservare le informazioni GC o Klass (l'ottimizzazione della classe è un'idea molto migliore dell'ottimizzazione degli Integer, di cui non me ne può fregare di meno). La modifica del puntatore richiederebbe il codice shift / cmp / jnz (o qualcosa del genere) prima di caricare ogni puntatore. Il ramo probabilmente non sarà predetto molto bene dall'hardware (dal momento che può essere sia Tipo valore che Oggetto normale) e porterebbe a un calo delle prestazioni.
bestsss

3
Ho fatto Smalltalk per alcuni anni. L'ottimizzazione era ancora piuttosto costosa, poiché per ogni operazione su un int dovevano smascherarli e riapplicarli. Attualmente java è alla pari con C quando si manipolano i numeri primitivi. Con smascherare + maschera sarà probabilmente> 30% più lento.
R. Moeller

9

Non riesco a credere che nessuno abbia menzionato quello che penso sia il motivo più importante: "int" è così, molto più facile da digitare di "Intero". Penso che le persone sottovalutino l'importanza di una sintassi concisa. Le prestazioni non sono in realtà un motivo per evitarle perché la maggior parte delle volte quando si usano i numeri è in indici di loop, e incrementare e confrontare quei costi non costa nulla in nessun loop non banale (sia che si usi int o Integer).

L'altro motivo è che è possibile ottenere NPE, ma è estremamente facile evitarlo con i tipi boxed (ed è garantito che sia evitato finché si inizializzano sempre su valori non nulli).

L'altro motivo è che (new Long (1000)) == (new Long (1000)) è falso, ma questo è solo un altro modo per dire che ".equals" non ha supporto sintattico per i tipi boxed (a differenza degli operatori <,> , =, ecc.), quindi torniamo al motivo della "sintassi più semplice".

Penso che l'esempio di loop non primitivo di Steve Yegge illustri molto bene il mio punto: http://sites.google.com/site/steveyegge2/language-trickery-and-ejb

Pensa a questo: con quale frequenza usi i tipi di funzione in linguaggi che hanno una buona sintassi per loro (come qualsiasi linguaggio funzionale, python, ruby ​​e persino C) rispetto a java in cui devi simularli usando interfacce come Runnable e Callable e classi senza nome.


8

Coppia di motivi per non sbarazzarsi di primitivi:

  • Compatibilità all'indietro.

Se viene eliminato, i vecchi programmi non verrebbero nemmeno eseguiti.

  • Riscrittura di JVM.

L'intera JVM dovrebbe essere riscritta per supportare questa nuova cosa.

  • Ingombro di memoria maggiore.

Dovresti archiviare il valore e il riferimento, che utilizza più memoria. Se disponi di una vasta gamma di byte, l'utilizzo di byte"s" è significativamente più piccolo di quello di Byte"s".

  • Problemi di puntatore null.

Dichiarare int iquindi fare cose con inon comporterebbe problemi, ma dichiarare Integer ie poi fare lo stesso comporterebbe un NPE.

  • Problemi di uguaglianza.

Considera questo codice:

Integer i1 = 5;
Integer i2 = 5;

i1 == i2; // Currently would be false.

Sarebbe falso Gli operatori dovrebbero essere sovraccaricati e ciò comporterebbe una grande riscrittura delle cose.

  • Lento

I wrapper di oggetti sono significativamente più lenti delle loro controparti primitive.


i1 == i2; sarebbe falso solo se i1> = 128. Quindi, l'esempio attuale è sbagliato
Geniy,

7

Gli oggetti sono molto più pesanti dei tipi primitivi, quindi i tipi primitivi sono molto più efficienti delle istanze delle classi wrapper.

I tipi primitivi sono molto semplici: ad esempio un int è 32 bit e occupa esattamente 32 bit in memoria e può essere manipolato direttamente. Un oggetto intero è un oggetto completo, che (come qualsiasi oggetto) deve essere memorizzato nell'heap e al quale è possibile accedere solo tramite un riferimento (puntatore). Molto probabilmente occupa anche più di 32 bit (4 byte) di memoria.

Detto questo, il fatto che Java abbia una distinzione tra tipi primitivi e non primitivi è anche un segno dell'età del linguaggio di programmazione Java. I linguaggi di programmazione più recenti non hanno questa distinzione; il compilatore di un tale linguaggio è abbastanza intelligente da capire da solo se stai usando valori semplici o oggetti più complessi.

Ad esempio, in Scala non ci sono tipi primitivi; esiste una classe Int per numeri interi e un Int è un oggetto reale (su cui è possibile eseguire metodi ecc.). Quando il compilatore compila il tuo codice, usa ints primitivi dietro le quinte, quindi usare un Int è altrettanto efficace di usare un int primitivo in Java.


1
Avrei supposto che JRE fosse abbastanza "intelligente" per farlo anche con le primitive avvolte in Java. Fallire.
Naftuli Kay

7

Oltre a ciò che altri hanno detto, le variabili locali primitive non sono allocate dall'heap, ma invece nello stack. Ma gli oggetti vengono allocati dall'heap e quindi devono essere raccolti.


3
Siamo spiacenti, questo è sbagliato. Una JVM intelligente può eseguire analisi di escape su tutte le allocazioni di oggetti e, se non possono evitarle, allocarle nello stack.
rlibby

2
Sì, questo sta iniziando a essere una caratteristica delle moderne JVM. Tra cinque anni, ciò che dici sarà vero per la maggior parte delle JVM allora in uso. Oggi non lo è. Ho quasi commentato questo, ma ho deciso di non commentarlo. Forse avrei dovuto dire qualcosa.
Eddie,

6

I tipi primitivi hanno molti vantaggi:

  • Codice più semplice da scrivere
  • Le prestazioni sono migliori poiché non si crea un'istanza di un oggetto per la variabile
  • Poiché non rappresentano un riferimento a un oggetto, non è necessario verificare la presenza di null
  • Utilizzare tipi primitivi a meno che non sia necessario sfruttare le funzionalità di boxe.

5

È difficile sapere che tipo di ottimizzazioni stanno avvenendo sotto le coperte.

Per uso locale, quando il compilatore ha abbastanza informazioni per effettuare ottimizzazioni escludendo la possibilità del valore null, mi aspetto che le prestazioni siano uguali o simili .

Tuttavia, le matrici di primitivi sono apparentemente molto diverse dalle raccolte di primitivi in ​​scatola. Ciò ha senso dato che sono possibili pochissime ottimizzazioni in profondità all'interno di una raccolta.

Inoltre, Integerha un sovraccarico logico molto più elevato rispetto a int: ora devi preoccuparti se int a = b + c;genera o meno un'eccezione.

Userei il più possibile le primitive e mi affiderei ai metodi di fabbrica e al box automatico per darmi i tipi di box semanticamente più potenti quando sono necessari.


5
int loops = 100000000;

long start = System.currentTimeMillis();
for (Long l = new Long(0); l<loops;l++) {
    //System.out.println("Long: "+l);
}
System.out.println("Milliseconds taken to loop '"+loops+"' times around Long: "+ (System.currentTimeMillis()- start));

start = System.currentTimeMillis();
for (long l = 0; l<loops;l++) {
    //System.out.println("long: "+l);
}
System.out.println("Milliseconds taken to loop '"+loops+"' times around long: "+ (System.currentTimeMillis()- start));

Millisecondi necessari per eseguire il ciclo "100000000" volte attorno a Long: 468

Millisecondi impiegati per eseguire il ciclo "100000000" volte in lungo: 31

In una nota a margine, non mi dispiacerebbe vedere qualcosa del genere trovarsi in Java.

Integer loop1 = new Integer(0);
for (loop1.lessThan(1000)) {
   ...
}

Dove il ciclo for incrementa automaticamente il ciclo1 da 0 a 1000 o

Integer loop1 = new Integer(1000);
for (loop1.greaterThan(0)) {
   ...
}

Dove il ciclo for diminuisce automaticamente il loop1 da 1000 a 0.


2
  1. Hai bisogno di primitivi per fare operazioni matematiche
  2. I primitivi richiedono meno memoria come indicato sopra e prestazioni migliori

Dovresti chiedere perché è richiesto il tipo di classe / oggetto

La ragione per avere il tipo di oggetto è di semplificarci la vita quando trattiamo le collezioni. Le primitive non possono essere aggiunte direttamente a Elenco / Mappa, ma è necessario scrivere una classe wrapper. Il tipo di classi Integer Readymade ti aiuta qui e ha molti metodi di utilità come Integer.pareseInt (str)


2

Concordo con le risposte precedenti, l'utilizzo di oggetti wrapper di primitive può essere costoso. Tuttavia, se le prestazioni non sono fondamentali nell'applicazione, si evitano gli overflow quando si utilizzano gli oggetti. Per esempio:

long bigNumber = Integer.MAX_VALUE + 2;

Il valore bigNumberè -2147483647 e ti aspetteresti che sia 2147483649. È un bug nel codice che sarebbe stato risolto facendo:

long bigNumber = Integer.MAX_VALUE + 2l; // note that '2' is a long now (it is '2L').

E bigNumbersarebbe 2147483649. Questo tipo di bug a volte è facile da perdere e può portare a comportamenti o vulnerabilità sconosciuti (vedere CWE-190 ).

Se si utilizzano oggetti wrapper, il codice equivalente non verrà compilato.

Long bigNumber = Integer.MAX_VALUE + 2; // Not compiling

Quindi è più facile fermare questo tipo di problemi usando gli oggetti wrapper delle primitive.

La tua domanda ha già una risposta tale, che rispondo solo per aggiungere un po 'più di informazioni non menzionate prima.


1

Perché JAVA esegue tutte le operazioni matematiche in tipi primitivi. Considera questo esempio:

public static int sumEven(List<Integer> li) {
    int sum = 0;
    for (Integer i: li)
        if (i % 2 == 0)
            sum += i;
        return sum;
}

Qui, non è possibile applicare operazioni di promemoria e unary plus sul tipo Intero (riferimento), il compilatore esegue unboxing ed esegue le operazioni.

Quindi, assicurati quante operazioni di autoboxing e unboxing avvengono nel programma java. Da allora, ci vuole tempo per eseguire queste operazioni.

In generale, è meglio mantenere argomenti di tipo Riferimento e risultato di tipo primitivo.


1

I tipi primitivi sono molto più veloci e richiedono molta meno memoria . Pertanto, potremmo voler preferire usarli.

D'altro canto, l'attuale specifica del linguaggio Java non consente l'utilizzo di tipi primitivi nei tipi con parametri (generici), nelle raccolte Java o nell'API Reflection.

Quando la nostra applicazione necessita di raccolte con un gran numero di elementi, dovremmo considerare l'utilizzo di array con il tipo più "economico" possibile.

* Per informazioni dettagliate consultare la fonte: https://www.baeldung.com/java-primitives-vs-objects


0

Per essere brevi: i tipi primitivi sono più veloci e richiedono meno memoria di quelli in scatola

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.