Quando utilizzare la classe primitiva vs in Java?


54

Vedo che Java ha booleano (classe) vs booleano (primitivo). Allo stesso modo, c'è un intero (classe) vs int (primitivo). Qual è la migliore pratica su quando utilizzare la versione primitiva rispetto alla classe? In pratica dovrei sempre usare la versione della classe a meno che non abbia un motivo specifico (prestazioni?) Per non farlo? Qual è il modo più comune e accettato per usarli?



Secondo me quelle non sono classi, sono scatole. Sono solo lì, quindi puoi usare le primitive dove sono richiesti gli oggetti, cioè nelle raccolte. Non puoi aggiungere due numeri interi (puoi fingere ma davvero java è auto boxing | unboxing i valori per te).
Stonemetal,

Risposte:


47

Nell'articolo 5 di Java efficace, dice Joshua Bloch

La lezione è chiara: preferisci i primitivi ai primitivi inscatolati e fai attenzione all'autoboxing involontario .

Un buon uso delle classi è quando le si utilizza come tipi generici (comprese le classi Collection, come elenchi e mappe) o quando si desidera trasformarle in altri tipi senza il cast implicito (ad esempio, la Integerclasse ha metodi doubleValue()o byteValue().

Modifica: il motivo di Joshua Bloch è:

// Hideously slow program! Can you spot the object creation?
public static void main(String[] args) {
    Long sum = 0L;
    for (long i = 0; i < Integer.MAX_VALUE; i++) {
         sum += i;
    }
    System.out.println(sum);
}

Questo programma ottiene la risposta giusta, ma è molto più lento di quanto dovrebbe essere, a causa di un errore tipografico di un carattere. La variabile sumviene dichiarata come Longinvece di a long, il che significa che il programma costruisce circa 2 ^ 31 Longistanze non necessarie (approssimativamente una per ogni volta che long iviene aggiunta alla Long sum). La modifica della dichiarazione di somma da Longa longriduce l'autonomia da 43 secondi a 6,8 secondi sulla mia macchina.


2
Sarebbe utile se tu elencassi le ragioni di Bloch, piuttosto che citare solo le sue conclusioni!
vaughandroid,

@Baqueta Ho modificato il post. Il motivo è la prestazione.
m3th0dman,

È un po 'più chiaro, grazie. Mi sento giustificato nel pubblicare la mia risposta ora. :)
vaughandroid

Potresti trovare interessante l'architettura lmax - "Ci è voluto un po 'più di intelligenza per salire di un altro ordine di grandezza. Ci sono diverse cose che il team LMAX ha trovato utili per arrivarci. Uno era quello di scrivere implementazioni personalizzate delle collezioni java progettate per essere attento alla cache e fare attenzione ai rifiuti. Un esempio di questo è l'utilizzo di java long primitivi come chiavi hashmap con un'implementazione Map supportata da array appositamente scritto . "

Sembra che qualcosa che JIT dovrebbe gestire
DeFreitas

28

La pratica standard è quella di seguire le primitive, a meno che tu non abbia a che fare con i generici (assicurati di essere a conoscenza di autoboxing e unboxing !).

Ci sono una serie di buoni motivi per seguire la convenzione:

1. Eviti semplici errori:

Ci sono alcuni casi sottili e non intuitivi che spesso catturano i principianti. Anche i programmatori esperti scivolano e fanno questi errori a volte (speriamo che questo sia seguito da imprecazioni quando eseguono il debug del codice e trovano l'errore!).

L'errore più comune sta usando a == binvece di a.equals(b). Le persone sono abituate a fare a == bcon le primitive, quindi è facile farlo quando si usano i wrapper di oggetti.

Integer a = new Integer(2);
Integer b = new Integer(2);
if (a == b) { // Should be a.equals(b)
    // This never gets executed.
}
Integer c = Integer.valueOf(2);
Integer d = Integer.valueOf(2);
if (c == d) { // Should be a.equals(b), but happens to work with these particular values!
    // This will get executed
}
Integer e = 1000;
Integer f = 1000;
if (e == f) { // Should be a.equals(b)
    // Whether this gets executed depends on which compiler you use!
}

2. Leggibilità:

Considera i seguenti due esempi. Molte persone direbbero che il secondo è più leggibile.

Integer a = 2;
Integer b = 2;
if (!a.equals(b)) {
    // ...
}
int c = 2;
int d = 2;
if (c != d) {
    // ...
}

3. Prestazioni:

Il fatto è che è più lento usare i wrapper Object per le primitive piuttosto che usare le primitive. Stai aggiungendo il costo dell'istanza dell'oggetto, delle chiamate di metodo, ecc. Alle cose che usi ovunque .

La citazione di "... diciamo circa il 97% delle volte: l'ottimizzazione prematura è la radice di tutti i mali" la citazione non si applica qui. Stava parlando di ottimizzazioni che rendono il codice (o sistema) più complicato - se sei d'accordo con il punto 2, questa è un'ottimizzazione che rende il codice meno complicato!

4. È la convenzione:

Se fai diverse scelte stilistiche rispetto al 99% degli altri programmatori Java là fuori, ci sono 2 aspetti negativi:

  • Troverai il codice di altre persone più difficile da leggere. Il 99% degli esempi / tutorial / ecc. Là fuori utilizzerà primitive. Ogni volta che ne leggi uno avrai il sovraccarico cognitivo in più di pensare a come apparirebbe nello stile a cui sei abituato.
  • Altre persone troveranno il tuo codice più difficile da leggere. Ogni volta che fai domande su Stack Overflow dovrai scorrere le risposte / i commenti chiedendo "perché non usi le primitive?". Se non mi credi, guarda le battaglie che le persone hanno su cose come il posizionamento delle parentesi, che non influisce nemmeno sul codice generato!

Normalmente elencherei alcuni contrappunti, ma onestamente non riesco a pensare a nessuna buona ragione per non andare qui alla convention!


2
Non suggerire alle persone di confrontare gli oggetti ==. Gli oggetti dovrebbero essere confrontati con equals().
Tulains Córdova,

2
@ user61852 Non lo stavo suggerendo come una cosa da fare, ma come un errore comune che viene fatto! Dovrei chiarirlo?
vaughandroid,

Sì, non dici che gli oggetti dovrebbero essere confrontati con equals()... dai loro una soluzione alternativa in modo che il confronto degli oggetti con ==i risultati attesi.
Tulains Córdova,

Buon punto. Modificato
vaughandroid,

Ho aggiunto equals()il secondo frammento di codice e ho cambiato il mio voto.
Tulains Córdova,

12

Di solito vado con i primitivi. Tuttavia, una peculiarità dell'uso di classi come Integered Booleanè la possibilità di assegnare nulla tali variabili. Ovviamente, ciò significa che devi fare sempre nullcontrolli, ma è ancora meglio ottenere una NullPointerException piuttosto che avere errori logici dovuti all'uso di alcune into booleanvariabili che non sono state inizializzate correttamente.

Naturalmente, dal momento che Java 8 è possibile (e probabilmente dovrebbe) fare un ulteriore passo avanti e, ad esempio, Integerè possibile utilizzare Optional<Integer>per variabili che potrebbero o meno avere un valore.

Inoltre, introduce la possibilità di utilizzare nullper assegnare a tali variabili un valore " sconosciuto " o " jolly ". Ciò può essere utile in alcune situazioni, ad esempio in Logica ternaria . Oppure potresti voler verificare se un determinato oggetto corrisponde a un modello; in questo caso è possibile utilizzare nullper quelle variabili nel modello che possono avere qualsiasi valore nell'oggetto.


2
(Non è stato il mio downvote, ma ...) Java rileva già variabili non inizializzate e non ti consente di leggere una variabile fino a quando ogni percorso di codice che porta al suo utilizzo ha assegnato definitivamente un valore. Quindi non guadagni molto dalla possibilità di assegnare nullcome predefinito. Al contrario: sarebbe meglio non "inizializzare" la variabile. L'impostazione di qualsiasi valore predefinito, anche null, arresta il compilatore ... ma gli impedisce anche di rilevare l'assenza di assegnazione utile lungo tutti i percorsi del codice. Pertanto, un errore che il compilatore avrebbe potuto rilevare passa al runtime.
cHao,

@cHao Ma cosa succede se non esiste un valore predefinito ragionevole con cui inizializzare la variabile? È possibile impostarlo su 0.0, oppure -1, oppure oppure Integer.MAX_VALUEoppure False, ma alla fine non si sa se si tratta del valore predefinito o di un valore effettivo assegnato a quella variabile. Nei casi in cui questo è importante, avere un nullvalore potrebbe essere più chiaro.
tobias_k

Non è più chiaro, è solo più facile dire al compilatore di non avvisarti della logica poco chiara fino a quando un bug non si è già propagato. : P Nei casi in cui non esiste un valore predefinito ragionevole, non inizializzare la variabile. Impostalo solo una volta che hai un valore ragionevole da mettere lì. Ciò consente a Java di arrestarti al momento della compilazione se il valore potrebbe non essere impostato correttamente.
cHao,

@cHao Volevo dire, ci potrebbero essere casi in cui non è possibile inizializzare la variabile e si deve fare con esso in fase di esecuzione. In tali casi, un "predefinito" come "null", che può essere chiaramente identificato come predefinito o "non inizializzato", potrebbe essere migliore di qualsiasi inizializzazione in fase di compilazione che potrebbe anche essere un valore valido.
tobias_k

Hai in mente un caso del genere? I casi a cui riesco a pensare sono ai confini dell'interfaccia (es. Come parametri o tipi di ritorno) ... ma anche lì, sarebbero molto meglio se racchiusi. (I nudi nullarrivano con tutta una serie di problemi, inclusa la paranoia nulla.) All'interno di una funzione, una variabile che potrebbe effettivamente non essere inizializzata nel punto di utilizzo indica in genere casi scoperti. (L'analisi delle assegnazioni definite è semplicistica, quindi sono possibili falsi positivi. Ma puoi spesso risolverli semplificando la logica, quindi.)
cHao

2

Nelle parole di laici:

Si utilizzano gli involucri quando è necessario aggiungere elementi alle raccolte.

Le raccolte non possono contenere primitive.


0

Java presenta il box automatico come ha sottolineato m3th0dman. Pensa al livello più basso possibile e vedrai che il boxing automatico (dentro o fuori) un valore primitivo implica cicli di clock spesi in alcune attività che non ti servono se lavori con tipi di dati nativi intorno alla tua applicazione.

Come regola generale, dovresti provare a usare tipi di dati nativi ogni volta che è possibile.

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.