Perché le raccolte Java non possono memorizzare direttamente i tipi di primitive?


123

Le raccolte Java memorizzano solo oggetti, non tipi primitivi; tuttavia possiamo memorizzare le classi wrapper.

Perché questo vincolo?


2
Questo vincolo fa schifo quando si ha a che fare con le primitive e si desidera utilizzare le code per l'invio e le velocità di invio sono molto elevate. In questo momento sto affrontando il problema dell'autoboxing che richiede troppo tempo.
JPM

Tecnicamente, i tipi primitivi sono oggetti (istanze singleton di tali per essere esatti), semplicemente non sono definiti da a class, piuttosto dalla JVM. L'istruzione int i = 1definisce un puntatore all'istanza singleton dell'oggetto che definisce intnella JVM, impostato sul valore 1definito da qualche parte nella JVM. Sì, i puntatori in Java - questo è solo astratto dall'implementazione del linguaggio. Le primitive non possono essere usate come generiche perché il linguaggio predicano che tutti i tipi generici devono essere del supertipo Object, quindi perché viene A<?>compilato A<Object>in fase di esecuzione.
Robert E Fry

1
I tipi primitivi @RobertEFry non sono oggetti in Java, quindi tutto ciò che hai scritto sulle istanze singleton e simili è fondamentalmente sbagliato e confuso. Suggerisco di leggere il capitolo "Tipi, valori e variabili" della specifica del linguaggio Java, che definisce cos'è un oggetto: "Un oggetto (§4.3.1) è un'istanza creata dinamicamente di un tipo di classe o un array creato dinamicamente. "
typeracer

Risposte:


99

È stata una decisione di progettazione Java e che alcuni considerano un errore. I contenitori vogliono che gli oggetti e le primitive non derivino da Object.

Questo è un punto in cui i progettisti .NET hanno imparato dalla JVM e hanno implementato tipi di valore e generici in modo tale che in molti casi la boxe venga eliminata. In CLR, i contenitori generici possono memorizzare i tipi di valore come parte della struttura del contenitore sottostante.

Java ha scelto di aggiungere il supporto generico al 100% nel compilatore senza il supporto della JVM. La JVM essendo quello che è, non supporta un oggetto "non oggetto". I generici Java ti consentono di fingere che non ci sia alcun wrapper, ma paghi comunque il prezzo delle prestazioni della boxe. Questo è IMPORTANTE per alcune classi di programmi.

La boxe è un compromesso tecnico e credo che siano dettagli di implementazione che trapelano nella lingua. L'autoboxing è un bel zucchero sintattico, ma è comunque una penalizzazione delle prestazioni. Se non altro, vorrei che il compilatore mi avvertisse quando si autobox. (Per quanto ne so, potrebbe ora, ho scritto questa risposta nel 2010).

Una buona spiegazione su SO sulla boxe: perché alcune lingue hanno bisogno di boxe e unboxing?

E critiche ai generici Java: perché alcuni sostengono che l'implementazione di Java dei generici è cattiva?

A difesa di Java, è facile guardare indietro e criticare. La JVM ha resistito alla prova del tempo ed è un buon design sotto molti aspetti.


6
Non è un errore, un compromesso scelto con cura che credo abbia servito molto bene Java.
DJClayworth

13
È stato un errore sufficiente che .NET abbia imparato da esso e abbia implementato l'autoboxing sin dall'inizio e generici a livello di VM senza sovraccarico di boxe. Il tentativo di correzione di Java era solo una soluzione a livello di sintassi che soffre ancora del calo delle prestazioni dell'autoboxing rispetto a nessun boxing. L'implementazione di Java ha mostrato scarse prestazioni con grandi strutture di dati.
codenheim

2
@mrjoltcola: IMHO, l'autoboxing predefinito è un errore, ma avrebbe dovuto esserci un modo per taggare variabili e parametri che dovrebbero automaticamente boxare i valori dati loro. Anche ora, penso che dovrebbe essere aggiunto un mezzo per specificare che determinate variabili o parametri non dovrebbero accettare valori appena inseriti in box [ad esempio, dovrebbe essere legale passare Object.ReferenceEqualsriferimenti di tipo Objectche identificano interi in box, ma non dovrebbe essere legale passare un valore intero]. L'unboxing automatico di Java è IMHO semplicemente sgradevole.
supercat

18

Semplifica l'implementazione. Poiché le primitive Java non sono considerate oggetti, è necessario creare una classe di raccolta separata per ciascuna di queste primitive (nessun codice modello da condividere).

Puoi farlo, ovviamente, basta vedere GNU Trove , Apache Commons Primitives o HPPC .

A meno che tu non abbia raccolte molto grandi, il sovraccarico per i wrapper non è abbastanza importante per le persone che si preoccupano (e quando hai raccolte primitive molto grandi, potresti voler dedicare lo sforzo a guardare usando / costruire una struttura dati specializzata per loro ).


11

È una combinazione di due fatti:

  • I tipi primitivi Java non sono tipi di riferimento (ad esempio un intnon è un fileObject )
  • Java esegue generici utilizzando la cancellazione del tipo dei tipi di riferimento (ad esempio un List<?>è davvero un List<Object>in fase di esecuzione)

Poiché entrambe le condizioni sono vere, le raccolte Java generiche non possono memorizzare direttamente i tipi primitivi. Per comodità, l'autoboxing è stato introdotto per consentire ai tipi primitivi di essere automaticamente inscatolati come tipi di riferimento. Non commettere errori al riguardo, tuttavia, le raccolte memorizzano ancora i riferimenti agli oggetti a prescindere.

Si sarebbe potuto evitare questo? Forse.

  • Se un intè un fileObject , non sono affatto necessari i tipi di scatola.
  • Se i generici non vengono eseguiti utilizzando la cancellazione del tipo, allora le primitive potrebbero essere state utilizzate per i parametri di tipo.

8

C'è il concetto di auto-boxing e auto-unboxing. Se si tenta di memorizzare un intin un List<Integer>compilatore Java lo convertirà automaticamente in un file Integer.


1
L'autoboxing è stato introdotto in Java 1.5 insieme a Generics.
Jeremy

1
Ma è una cosa in fase di compilazione; sintassi zucchero senza benefici in termini di prestazioni. L'autobox del compilatore Java, da qui la penalizzazione delle prestazioni rispetto alle implementazioni VM come .NET, i cui generici non implicano la boxe.
codenheim

1
@mrjoltcola: qual è il tuo punto? Stavo solo condividendo fatti, non discutendo.
Jeremy

3
Il mio punto è che è importante sottolineare la differenza tra sintassi e prestazioni. Considero anche i miei commenti condivisione dei fatti, non argomenti. Grazie.
codenheim

2

Non è proprio un vincolo, vero?

Considera se vuoi creare una raccolta che memorizzi valori primitivi. Come scriveresti una raccolta in grado di memorizzare int, float o char? Molto probabilmente ti ritroverai con più raccolte, quindi avrai bisogno di un intlist e di un charlist ecc.

Sfruttando la natura orientata agli oggetti di Java, quando si scrive una classe di raccolta, è possibile memorizzare qualsiasi oggetto, quindi è necessaria solo una classe di raccolta. Questa idea, il polimorfismo, è molto potente e semplifica notevolmente la progettazione delle librerie.


7
"Come scriveresti una raccolta in grado di memorizzare int, float o char?" - Con generici / modelli correttamente implementati come altri linguaggi che non pagano la penalità di fingere che tutto sia un oggetto.
codenheim

Quasi mai in sei anni Java volevo archiviare una raccolta di primitive. Anche nei pochi casi in cui avrei potuto desiderare, i costi di tempo e spazio aggiuntivi per l'utilizzo degli oggetti di riferimento sono stati trascurabili. In particolare, trovo che molte persone pensano di volere Map <int, T>, dimenticando che un array farà quel trucco molto bene.
DJClayworth

2
@DJClayworth Funziona bene solo se i valori delle chiavi non sono sparsi. Ovviamente, potresti usare un array ausiliario per tenere traccia delle chiavi, ma questo ha i suoi problemi: un accesso relativamente efficiente richiederebbe mantenere entrambi gli array ordinati in base all'ordine delle chiavi per consentire la ricerca binaria, che a sua volta comporterebbe l'inserimento e la cancellazione inefficiente a meno che l'inserimento / eliminazione non sia modellato in modo tale che gli elementi inseriti finiscano dove si trovava un elemento precedentemente eliminato e / o alcuni buffer sono intervallati negli array, ecc. Ci sono risorse disponibili, ma sarebbe bello averlo Java stesso.
JAB

@JAB In realtà funziona bene se le chiavi sono sparse, ha solo bisogno di più memoria rispetto a se non sono sparse. E se le chiavi sono sparse, significa che non ce ne sono molte e l'uso di Integer come chiave funziona bene. Usa l'approccio che richiede meno memoria. O come ti senti se non ti interessa.
DJClayworth

0

Penso che potremmo vedere progressi in questo spazio nel JDK possibilmente in Java 10 basato su questo JEP - http://openjdk.java.net/jeps/218 .

Se vuoi evitare le primitive di boxe nelle collezioni oggi, ci sono diverse alternative di terze parti. Oltre alle opzioni di terze parti menzionate in precedenza, ci sono anche Eclipse Collections , FastUtil e Koloboke .

Qualche tempo fa è stato pubblicato anche un confronto di mappe primitive con il titolo: Large HashMap overview: JDK, FastUtil, Goldman Sachs, HPPC, Koloboke, Trove . La libreria GS Collections (Goldman Sachs) è stata migrata alla Eclipse Foundation e ora è Eclipse Collections.


0

Il motivo principale è la strategia di progettazione java. ++ 1) le raccolte richiedono oggetti per la manipolazione e le primitive non sono derivate da un oggetto, quindi questo può essere l'altro motivo. 2) I tipi di dati primitivi Java non sono tipi di riferimento per es. int non è un oggetto.

Superare:-

abbiamo il concetto di auto-boxing e auto-unboxing. quindi, se stai cercando di memorizzare i tipi di dati primitivi, il compilatore lo convertirà automaticamente in oggetto di quella classe di dati primitiva.

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.