Domanda

La miscelazione dell'uso dei tipi di dati primitivi e delle rispettive classi wrapper, in Java, può portare a molti bug. L'esempio seguente illustra il problema:

int i = 4;
...
if (i == 10)
  doStuff();

In seguito si immagina che si desidera che la variabile i sia definita o indefinita, quindi si modifica l'istanza sopra in:

Integer i = null;

Ora il controllo dell'uguaglianza fallisce.

È buona pratica Java usare sempre le classi wrapper primitive? Ovviamente eliminerebbe presto alcuni bug, ma quali sono gli svantaggi di questo? Influisce sulle prestazioni o sul footprint di memoria dell'applicazione? Ci sono dei getchas subdoli?

È stato utile?

Soluzione

L'uso dei tipi inscatolati presenta sia problemi di prestazioni che di memoria.

Quando si effettuano i confronti (ad es. (i == 10) ), java deve deselezionare il tipo prima di effettuare il confronto. Anche usando i.equals (TEN) usa una chiamata di metodo, che è più costosa e (IMO) più brutta della sintassi ==.

Per quanto riguarda la memoria, l'oggetto deve essere archiviato nell'heap (che influisce anche sulle prestazioni) oltre a memorizzare il valore stesso.

Un subdolo gotcha? i.equals (j) quando sono null .

Uso sempre le primitive, tranne quando può essere null , ma in questi casi cerco sempre null prima del confronto.

Altri suggerimenti

In primo luogo, passare dall'uso di una primitiva all'utilizzo di un oggetto solo per ottenere la possibilità di impostarlo su null è probabilmente una cattiva decisione di progettazione. Spesso ho discussioni con i miei colleghi sul fatto che null sia o meno un valore sentinella, e la mia opinione è di solito che non lo sia (e quindi non dovrebbe essere proibito come dovrebbero essere i valori sentinella), ma in questo caso particolare andrai fuori dal tuo modo di usarlo come valore sentinella. Per favore, no. Crea un valore booleano che indichi se il tuo numero intero è valido o crea un nuovo tipo che racchiuda insieme il valore booleano e intero.

Di solito, quando utilizzo le versioni più recenti di Java, trovo che non ho bisogno di creare o eseguire il cast esplicito per le versioni di oggetti delle primitive a causa del supporto di auto-boxing che è stato aggiunto da tempo in 1.5 (forse 1.5 stesso).

Suggerirei di usare sempre le primitive a meno che tu non abbia davvero il concetto di " null "

Sì, la VM esegue il boxing automatico e tutto il resto ora, ma può portare ad alcuni casi davvero più strani in cui otterrai un'eccezione puntatore null su una riga di codice che non ti aspetti, e devi iniziare facendo controlli nulli su ogni operazione matematica. Puoi anche iniziare a ottenere comportamenti non ovvi se inizi a mescolare tipi e ad assumere comportamenti autoboxing strani.

Per float / double puoi considerare NaN come null, ma ricorda che NaN! = NaN quindi hai ancora bisogno di controlli speciali come! Float.isNaN (x).

Sarebbe davvero bello se ci fossero raccolte che supportano i tipi primitivi invece di dover perdere tempo / spese generali di boxe.

Nel tuo esempio, l'istruzione if andrà bene fino a quando non vai oltre 127 (poiché l'autoboxing integer memorizzerà nella cache valori fino a 127 e restituirà la stessa istanza per ogni numero fino a questo valore)

Quindi è peggio di quanto tu lo presenti ...

if( i == 10 )

funzionerà come prima, ma

if( i == 128 )

fallirà. È per ragioni come queste che creo sempre esplicitamente oggetti quando ne ho bisogno e tendo ad attenermi alle variabili primitive, se possibile

Questi tipi di POD Java sono lì per un motivo. Oltre al sovraccarico, non è possibile eseguire le normali operazioni con gli oggetti. Un numero intero è un oggetto, che deve essere allocato e raccolta dei rifiuti. Un int non lo è.

Se quel valore può essere vuoto, potresti scoprire che nel tuo disegno hai bisogno di qualcos'altro.

Esistono due possibilità: o il valore sono solo dati (il codice non agirà in modo diverso se è compilato o meno), oppure sta effettivamente indicando che hai due diversi tipi di oggetto qui (il codice agisce in modo diverso se esiste un valore diverso da null)

Se si tratta solo di dati per la visualizzazione / archiviazione, potresti prendere in considerazione l'utilizzo di un DTO reale, uno che non lo ha affatto come membro di prima classe. Questi generalmente avranno un modo per verificare se un valore è stato impostato o meno.

Se controlli il null a un certo punto, potresti voler usare una sottoclasse perché quando c'è una differenza, di solito ce ne sono altre. Almeno vuoi un modo migliore per indicare la tua differenza rispetto a " if primitiveIntValue == null " ;, ciò non significa davvero nulla.

Non passare a non primitivi solo per ottenere questa funzione. Utilizzare un valore booleano per indicare se il valore è stato impostato o meno. Se non ti piace quella soluzione e sai che i tuoi numeri interi avranno un limite ragionevole (o non ti preoccupi del fallimento occasionale) usa un valore specifico per indicare "non inizializzato", come Integer.MIN_VALUE. Ma questa è una soluzione molto meno sicura del booleano.

Quando sei arrivato a quel punto 'Più tardi', un po 'più di lavoro doveva essere realizzato durante il refactoring. Usa le primitive quando possibile. (Periodo di capitale) Quindi creare POJO se sono necessarie ulteriori funzionalità. Le classi wrapper primitive, a mio avviso, sono utilizzate al meglio per i dati che devono viaggiare attraverso il filo, ovvero app in rete. Consentire null come valori accettabili provoca mal di testa quando un sistema "cresce". Troppo codice sprecato o perso, a guardia di quelli che dovrebbero essere semplici confronti.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top