2009-06-24 12 views
27

Questa non è una questione di cosa è il pugilato e l'unboxing, è piuttosto perché lingue come Java e C# ne hanno bisogno?Perché alcune lingue richiedono Boxing e Unboxing?

Sono molto familiare con C++, STL e Boost.

In C++ potrei scrivere qualcosa di simile molto facilmente,

std::vector<double> dummy; 

Ho una certa esperienza con Java, ma ero davvero sorpreso, perché ho dovuto scrivere qualcosa del genere,

ArrayList<Double> dummy = new ArrayList<Double>(); 

La mia domanda, perché dovrebbe essere un oggetto, cosa è tecnicamente così difficile includere tipi primitivi quando si parla di Generics?

+0

Provenendo da C++ a Java, sono rimasto completamente completamente scioccato quando ho scoperto questo fatto ... – GuLearn

risposta

46

cosa è tecnicamente così difficile includere i tipi primitivi quando si parla di Generics?

Nel caso di Java, è a causa del modo in cui i generici funzionano. In Java, i generici sono un trucco in fase di compilazione, che impedisce di inserire un oggetto Image in un ArrayList<String>. Tuttavia, i generici di Java sono implementati con la cancellazione dei tipi: le informazioni di tipo generico vengono perse durante l'esecuzione. Questo era per ragioni di compatibilità, perché i farmaci generici venivano aggiunti abbastanza tardi nella vita di Java. Ciò significa che, in fase di esecuzione, un ArrayList<String> è in effetti un ArrayList<Object> (o meglio: solo ArrayList che si aspetta e restituisce Object in tutti i suoi metodi) che viene automaticamente convertito in String quando si recupera un valore.

Ma poiché int non deriva da Object, non si può mettere in un ArrayList che prevede (in fase di esecuzione) Object e non si può lanciare un Object a int sia. Ciò significa che la primitiva int deve essere racchiusa in un tipo che eredita da Object, ad esempio Integer.

C# ad esempio, funziona in modo diverso. Generics in C# vengono applicati anche in fase di esecuzione e non è richiesta alcuna boxing con uno List<int>. La box in C# si verifica solo quando si tenta di memorizzare un tipo di valore come int in una variabile di tipo di riferimento come object. Dato che int in C# eredita da Object in C#, la scrittura object obj = 2 è perfettamente valida, tuttavia l'int sarà in box, operazione eseguita automaticamente dal compilatore (nessun tipo di riferimento Integer è esposto all'utente o altro).

+0

solo nella speranza che la mia domanda venga notata oso chiedere: perché java non ha implementato la primitiva per il generico attraverso l'autoboxing? Voglio dire nel caso in cui la lista compilazione si sarebbe compilata automaticamente su Integer. – Dedyshka

11

Boxe e unboxing sono una necessità nata dal modo in cui le lingue (come C# e Java) implementano le loro strategie di allocazione della memoria.

Alcuni tipi sono allocati nello stack e altri nello heap. Per trattare un tipo allocato allo stack come un tipo di allocazione dell'heap, è necessario il parallelismo per spostare il tipo assegnato allo stack sull'heap. Unboxing è il processo inverso.

In C# Tipi stack allocato sono chiamati tipi di valore (ad esempio System.Int32 e System.DateTime) e tipi heap allocati sono chiamati tipi di riferimento (ad esempio System.Stream e System.String).

In alcuni casi è vantaggioso poter trattare un tipo di valore come un tipo di riferimento (la riflessione è un esempio) ma nella maggior parte dei casi è meglio evitare il pugilato e il disimballaggio.

+4

Attento con i tipi di valore e l'allocazione basata sullo stack. Eric Lippert ha pubblicato due fantastici post su questo argomento: http://blogs.msdn.com/ericlippert/archive/2009/04/27/the-stack-is-an-implementation-detail.aspx e http: // blogs .msdn.com/ericlippert/archive/2009/05/04/the-stack-is-an-implementation-detail-part-two.aspx – Joey

+2

AFAIK, C# non utilizza il box automatico su contenitori generici a meno che non si specifichi qualcosa come Elenco . I generici di C# funzionano in modo simile al C++ per i tipi di valore. http://msdn.microsoft.com/en-us/library/ms379564(VS.80).aspx –

2

Credo che questo sia anche perché le primitive non ereditano da Object. Supponiamo che tu abbia un metodo che vuole essere in grado di accettare qualsiasi cosa come parametro, es.

class Printer { 
    public void print(Object o) { 
     ... 
    } 
} 

Potrebbe essere necessario passare un semplice valore di base a tale metodo, come:

printer.print(5); 

Si sarebbe in grado di farlo senza la boxe/unboxing, perché 5 è un primitivo e non è un Oggetto. È possibile sovraccaricare il metodo di stampa per ciascun tipo di primitivo per abilitare tale funzionalità, ma è un dolore.

1

In Java e C# (a differenza di C++) tutto estende Object, quindi le classi di raccolta come ArrayList possono contenere Object o qualsiasi dei suoi discendenti (in pratica qualsiasi cosa).

Per motivi di prestazioni, tuttavia, le primitive in java oi tipi di valore in C# hanno ricevuto uno stato speciale. Non sono oggetti. Non è possibile fare qualcosa di simile (in Java):

7.toString() 

Anche se toString è un metodo su Oggetto. Al fine di colmare questo cenno con le prestazioni, sono stati creati oggetti equivalenti. AutoBoxing rimuove il codice boilerplate dal dover mettere una primitiva nella sua classe wrapper e rimuoverla di nuovo, rendendo il codice più leggibile.

La differenza tra i tipi di valore e gli oggetti in C# è più grigia.Vedi here su come sono diversi.

+0

In C#, le primitive sono implementate come structs (int è definito ad esempio in Int32 nello spazio dei nomi System) che hanno metodi così 7.ToString() funzionerà correttamente. E mentre tutto in C# deriva da Object, questo non è il caso di Java. – JulianR

+3

@JulianR: in particolare, tutti i tipi di valore CLR ereditano da System.ValueType che a sua volta eredita da System.Object. System.ValueType sovrascrive molti dei metodi virtuali di System.Object evitando così il pugilato quando vengono chiamati questi metodi. Gli unici metodi che causano il pugilato per un tipo di valore sono Object.GetType e Object.MemberwiseClone. –

2

Posso solo dire per Java perché non supporta i tipi di primitve in generici.

Prima c'era il problema che la domanda per supportare questo ogni volta portava alla discussione se java dovesse anche avere tipi primitivi. Quale naturalmente ostacolato la discussione della domanda reale.

In secondo luogo, il motivo principale per non includerlo era il fatto che volevano la compatibilità con le versioni binarie in modo tale che fosse eseguito senza modifiche su una VM non consapevole dei generici. Questo motivo di compatibilità retroattiva/migrazione è anche il motivo per cui ora l'API Collections supporta i generici e rimane invariata e non c'è (come in C# quando hanno introdotto i generici) un nuovo set completo di un'API Collection generica e consapevole.

La compatibilità è stata eseguita utilizzando ersure (informazioni sui parametri di tipo generico rimossi al momento della compilazione) che è anche il motivo per cui si ottengono così tanti avvisi cast non verificati in java.

È ancora possibile aggiungere generici reificati ma non è così facile. Aggiungendo semplicemente le informazioni sul tipo aggiungi runtime invece di rimuoverlo non funzionerà in caso di interruzione della compatibilità binaria di origine & (non puoi continuare a utilizzare i tipi non elaborati e non puoi chiamare codice compilato esistente perché non hanno i metodi corrispondenti).

L'altro approccio è quello C# ha scelto: vedi sopra

e automatizzato autoboxing/unboxing non è stato supportato per questo caso d'uso a causa autoboxing costi troppo.

Java theory and practice: Generics gotchas

1

Ogni oggetto non stringa non array memorizzato sul mucchio contiene un'intestazione 8 o 16 byte (dimensioni per 32/sistemi a 64 bit), seguito dal contenuto di tale oggetto di pubblico e campi privati. Array e stringhe hanno l'intestazione sopra, più altri byte che definiscono la lunghezza dell'array e la dimensione di ogni elemento (e possibilmente il numero di dimensioni, la lunghezza di ogni dimensione extra, ecc.), Seguito da tutti i campi del primo elemento, quindi tutti i campi del secondo, ecc. Dato un riferimento ad un oggetto, il sistema può facilmente esaminare l'intestazione e determinare di che tipo si tratta.

Le posizioni di memoria di tipo di riferimento contengono un valore di quattro o otto byte che identifica univocamente un oggetto archiviato nell'heap. Nelle attuali implementazioni, quel valore è un puntatore, ma è più facile (e semanticamente equivalente) pensarlo come un "ID oggetto".

Le posizioni di memoria di tipo valore contengono il contenuto dei campi del tipo valore, ma non hanno alcuna intestazione associata. Se il codice dichiara una variabile di tipo Int32, non è necessario archiviare le informazioni con quello Int32 dicendo di cosa si tratta. Il fatto che tale posizione contenga un Int32 viene effettivamente memorizzato come parte del programma e quindi non deve essere memorizzato nella posizione stessa. Questo rappresenta un grande risparmio se, ad esempio, uno ha un milione di oggetti ciascuno dei quali ha un campo di tipo Int32. Ciascuno degli oggetti che contengono lo Int32 ha un'intestazione che identifica la classe in grado di gestirlo. Poiché una copia di quel codice classe può operare su una qualsiasi delle milioni di istanze, il fatto che il campo sia un Int32 parte del codice è molto più efficiente che avere lo spazio di archiviazione per ognuno di quei campi include informazioni su ciò che è .

La boxe è necessaria quando viene effettuata una richiesta per passare il contenuto di una posizione di memorizzazione di tipo valore al codice che non sa aspettarsi quel particolare tipo di valore. Il codice che si aspetta oggetti di tipo sconosciuto può accettare un riferimento a un oggetto archiviato nell'heap. Poiché ogni oggetto archiviato nell'heap ha un'intestazione che identifica il tipo di oggetto, il codice può utilizzare quell'intestazione ogni volta che è necessario utilizzare un oggetto in un modo che richiederebbe conoscerne il tipo.

Si noti che in .net, è possibile dichiarare quelle che vengono chiamate classi e metodi generici. Ciascuna di tali dichiarazioni genera automaticamente una famiglia di classi o metodi che sono identici tranne per il tipo di oggetto sul quale si aspettano di agire. Se si passa un Int32 a una routine DoSomething<T>(T param), verrà generata automaticamente una versione della routine in cui ogni istanza di tipo T viene effettivamente sostituita con Int32. Quella versione della routine saprà che ogni posizione di archiviazione dichiarata come tipo T detiene un Int32, così come nel caso in cui una routine è stata codificata per utilizzare una posizione di archiviazione Int32, non sarà necessario memorizzare le informazioni sul tipo con quelle posizioni stesse.