Domanda

Questa non è una domanda su cosa sia la boxe e unboxing, è piuttosto perché lingue come Java e C # ne hanno bisogno?

Ho molta familiarità con C ++, STL e Boost.

In C ++ potrei scrivere qualcosa del genere molto facilmente,

std::vector<double> dummy;

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

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

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

È stato utile?

Soluzione

  

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

Nel caso di Java, è a causa del modo in cui funzionano i generici. In Java, i generici sono un trucco in fase di compilazione, che ti impedisce di mettere un Image oggetto in un ArrayList<String>. Tuttavia, i generici di Java sono implementati con la cancellazione del tipo: le informazioni sul tipo generico vengono perse durante il runtime. Questo per motivi di compatibilità, perché i generici furono aggiunti abbastanza tardi nella vita di Java. Ciò significa che, in fase di runtime, un ArrayList<Object> è effettivamente un ArrayList (o migliore: solo Object che prevede e restituisce String in tutti i suoi metodi) che si lancia automaticamente su int quando si recupera un valore.

Ma poiché Integer non deriva da List<int>, non puoi inserirlo in una ArrayList che si aspetta (in fase di esecuzione) object e non puoi nemmeno lanciare un object obj = 2 in <=> . Ciò significa che la primitiva <=> deve essere racchiusa in un tipo che eredita da <=>, come <=>.

C # per esempio, funziona in modo diverso. Anche i generici in C # vengono applicati in fase di esecuzione e non è richiesta la boxe con <=>. Il boxing in C # si verifica solo quando si tenta di memorizzare un tipo di valore come <=> in una variabile del tipo di riferimento come <=>. Poiché <=> in C # eredita da <=> in C #, la scrittura di <=> è perfettamente valida, tuttavia int verrà inscatolato, il che viene fatto automaticamente dal compilatore (nessun <=> tipo di riferimento viene esposto all'utente o altro ).

Altri suggerimenti

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

Alcuni tipi sono allocati nello stack e altri nell'heap. Per trattare un tipo allocato in stack come un tipo allocato in heap, è necessario il boxing per spostare il tipo allocato in stack sull'heap. Unboxing è il processo inverso.

In C # i tipi allocati in stack sono chiamati tipi di valore (ad es. System.Int32 e System.DateTime ) e i tipi allocati in heap sono chiamato tipi di riferimento (ad es. 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 boxe e unboxing.

Credo che ciò sia dovuto al fatto che le primitive non ereditano dall'Object. Supponiamo di avere un metodo che vuole essere in grado di accettare qualsiasi cosa come parametro, ad es.

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

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

printer.print(5);

Saresti in grado di farlo senza boxe / unboxing, perché 5 è una primitiva e non un oggetto. Potresti sovraccaricare il metodo di stampa per ogni tipo di primitiva per abilitare tale funzionalità, ma è una seccatura.

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

In primo luogo c'era il problema che la domanda a supporto di questo ogni volta portava alla discussione se java dovesse anche avere tipi primitivi. Il che ovviamente ha ostacolato la discussione della vera domanda.

In secondo luogo, il motivo principale per non includerlo era che volevano la compatibilità binaria con le versioni precedenti, in modo che funzionasse senza modifiche su una VM non consapevole dei generici. Questo motivo di compatibilità con le versioni precedenti / compatibilità con le migrazioni è anche il motivo per cui ora l'API delle raccolte supporta i generici ed è rimasta la stessa e non esiste (come in C # quando hanno introdotto i generici) un nuovo set completo di un'API di raccolta con riconoscimento generico.

La compatibilità è stata fatta usando ersure (informazioni sui parametri di tipo generico rimossi al momento della compilazione), che è anche la ragione per cui in java ricevi così tanti avvisi di cast non controllati.

Potresti ancora aggiungere generici reificati ma non è così facile. Basta aggiungere le informazioni sul tipo aggiungere runtime invece di rimuoverlo non funzionerà perché rompe sorgente e amp; compatibilità binaria (non è possibile continuare a utilizzare tipi non elaborati e non è possibile chiamare il codice compilato esistente perché non dispongono dei metodi corrispondenti).

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

E il boxbox automatico / unboxing non era supportato per questo caso d'uso perché il box automatico costa troppo.

Teoria e pratica di Java: Generics gotchas

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

Per motivi di prestazioni, tuttavia, alle primitive in java o ai tipi di valore in C # è stato assegnato uno stato speciale. Non sono oggetti. Non puoi fare qualcosa del genere (in Java):

 7.toString()

Anche se toString è un metodo su Object. Al fine di collegare questo cenno alle prestazioni, sono stati creati oggetti equivalenti. AutoBoxing rimuove il codice boilerplate di dover inserire una primitiva nella sua classe wrapper ed estrarla di nuovo, rendendo il codice più leggibile.

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

Ogni oggetto non stringa non array archiviato nell'heap contiene un'intestazione di 8 o 16 byte (dimensioni per sistemi a 32/64 bit), seguito dal contenuto dei campi pubblici e privati ??di quell'oggetto. Le matrici e le stringhe hanno l'intestazione precedente, oltre ad alcuni altri byte che definiscono la lunghezza dell'array e le dimensioni di ciascun elemento (e possibilmente il numero di dimensioni, la lunghezza di ogni dimensione aggiuntiva, ecc.), Seguite da tutti i campi del primo elemento, quindi tutti i campi del secondo, ecc. Dato un riferimento a un oggetto, il sistema può facilmente esaminare l'intestazione e determinare di che tipo è.

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

Le posizioni di archiviazione del tipo di valore contengono i contenuti dei campi del tipo di valore, ma non hanno alcuna intestazione associata. Se il codice dichiara una variabile di tipo Int32 , non è necessario archiviare informazioni con quel Int32 che dice di cosa si tratta. Il fatto che quella posizione contenga un Int32 viene effettivamente memorizzato come parte del programma e quindi non deve essere archiviato nella posizione stessa. Questo rappresenta un grande risparmio se, ad esempio, uno ha un milione di oggetti ognuno dei quali ha un campo di tipo Int32 . Ciascuno degli oggetti che contengono il Int32 ha un'intestazione che identifica la classe che può gestirlo. Dal momento che una copia di quel codice di classe può operare su una qualsiasi delle milioni di istanze, avere il fatto che il campo è un Int32 essere parte del codice è molto più efficiente che avere l'archiviazione per ognuna di quelle campi includono informazioni su ciò che è.

Il boxing è necessario quando viene effettuata una richiesta per passare il contenuto di una posizione di archiviazione del tipo di valore al codice che non sa di aspettarsi quel particolare tipo di valore. Il codice che prevede oggetti di tipo sconosciuto può accettare un riferimento a un oggetto memorizzato nell'heap. Poiché ogni oggetto memorizzato nell'heap ha un'intestazione che identifica il tipo di oggetto, il codice può usare quell'intestazione ogni volta che è necessario usare un oggetto in un modo che richiederebbe conoscerne il tipo.

Notare che in .net è possibile dichiarare quelli che sono chiamati metodi e classi generici. Ciascuna di queste dichiarazioni genera automaticamente una famiglia di classi o metodi identici, tranne per il tipo di oggetto su cui si aspettano di agire. Se si passa un Int32 a una DoSomething < T > (T param) di routine, ciò genererà automaticamente una versione della routine in cui ogni istanza di tipo T viene effettivamente sostituito con Int32 . Quella versione della routine saprà che ogni posizione di archiviazione dichiarata come T contiene un Int32 , così come nel caso in cui una routine fosse hardcoded per usare un < code> Int32 posizione di archiviazione, non sarà necessario memorizzare le informazioni sul tipo con tali posizioni stesse

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