Domanda

Utilizzo principalmente Java e i generici sono relativamente nuovi.Continuo a leggere che Java ha preso la decisione sbagliata o che .NET ha implementazioni migliori, ecc.eccetera.

Quindi, quali sono le principali differenze tra C++, C# e Java nei generici?Pro/contro di ciascuno?

È stato utile?

Soluzione

Aggiungerò la mia voce al rumore e cercherò di chiarire le cose:

I generici C# ti consentono di dichiarare qualcosa di simile.

List<Person> foo = new List<Person>();

e quindi il compilatore ti impedirà di inserire cose che non lo sono Person nell'elenco.
Dietro le quinte il compilatore C# sta semplicemente mettendo List<Person> nel file dll .NET, ma in fase di esecuzione il compilatore JIT va e crea un nuovo set di codice, come se avessi scritto una classe elenco speciale solo per contenere le persone - qualcosa come ListOfPerson.

Il vantaggio di questo è che lo rende davvero veloce.Non c'è casting o altro, e poiché la dll contiene le informazioni di cui questo è un elenco Person, altro codice che lo esamina in seguito utilizzando la riflessione può dire che contiene Person oggetti (così ottieni intellisense e così via).

Lo svantaggio è che il vecchio codice C# 1.0 e 1.1 (prima che aggiungessero i generici) non comprende questi nuovi List<something>, quindi devi riconvertire manualmente le cose al vecchio stile List per interagire con loro.Questo non è un grosso problema perché il codice binario C# 2.0 non è compatibile con le versioni precedenti.L'unica volta che ciò accadrà è se stai aggiornando un vecchio codice C# 1.0/1.1 a C# 2.0

Java Generics ti consente di dichiarare qualcosa di simile.

ArrayList<Person> foo = new ArrayList<Person>();

In superficie sembra lo stesso, e in un certo senso lo è.Il compilatore ti impedirà anche di inserire cose che non lo sono Person nell'elenco.

La differenza è ciò che accade dietro le quinte.A differenza di C#, Java non va a creare uno speciale ListOfPerson - usa solo il semplice vecchio ArrayList che è sempre stato in Java.Quando tiri fuori le cose dall'array, il solito Person p = (Person)foo.get(1); il casting-dance deve ancora essere fatto.Il compilatore ti sta risparmiando la pressione dei tasti, ma la velocità di hit/casting è ancora sostenuta proprio come sempre.
Quando le persone menzionano "Cancellazione tipo" è di questo che stanno parlando.Il compilatore inserisce i cast per te e poi "cancella" il fatto che dovrebbe essere un elenco di Person non solo Object

Il vantaggio di questo approccio è che il vecchio codice che non comprende i farmaci generici non deve preoccuparsene.Ha ancora a che fare con lo stesso vecchio ArrayList come ha sempre fatto.Questo è più importante nel mondo Java perché volevano supportare la compilazione del codice utilizzando Java 5 con generici e farlo funzionare su vecchie JVM 1.4 o precedenti, di cui Microsoft ha deliberatamente deciso di non preoccuparsi.

Lo svantaggio è il calo di velocità di cui ho parlato prima, e anche perché non esiste ListOfPerson pseudo-classe o qualcosa del genere che entra nei file .class, codice che lo esamina in seguito (con riflessione, o se lo estrai da un'altra raccolta in cui è stato convertito in Object o così via) non può dire in alcun modo che debba essere un elenco contenente solo Person e non solo qualsiasi altro elenco di array.

I modelli C++ ti consentono di dichiarare qualcosa di simile

std::list<Person>* foo = new std::list<Person>();

Assomiglia ai generici C# e Java e farà quello che pensi che dovrebbe fare, ma dietro le quinte stanno accadendo cose diverse.

Ha molto in comune con i generici C# in quanto crea speciali pseudo-classes piuttosto che semplicemente buttare via le informazioni sul tipo come fa Java, ma è un paio di cose completamente diverse.

Sia C# che Java producono output progettato per macchine virtuali.Se scrivi del codice che ha un file Person classe in esso, in entrambi i casi alcune informazioni su a Person class andrà nel file .dll o .class e JVM/CLR farà cose con questo.

C++ produce codice binario x86 grezzo.Tutto è non un oggetto e non esiste una macchina virtuale sottostante che debba conoscere a Person classe.Non c'è boxing o unboxing e le funzioni non devono appartenere a classi o altro.

Per questo motivo, il compilatore C++ non pone restrizioni su ciò che puoi fare con i modelli: praticamente qualsiasi codice che potresti scrivere manualmente, puoi ottenere modelli da scrivere per te.
L'esempio più ovvio è l'aggiunta di cose:

In C# e Java, il sistema dei generici deve sapere quali metodi sono disponibili per una classe e deve trasmetterli alla macchina virtuale.L'unico modo per dirlo è codificare la classe effettiva o utilizzare le interfacce.Per esempio:

string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }

Quel codice non verrà compilato in C# o Java perché non riconosce il tipo T in realtà fornisce un metodo chiamato Nome().Devi dirlo - in C# in questo modo:

interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }

E poi devi assicurarti che le cose che passi a addNames implementino l'interfaccia IHasName e così via.La sintassi Java è diversa (<T extends IHasName>), ma soffre degli stessi problemi.

Il caso "classico" di questo problema è provare a scrivere una funzione che faccia questo

string addNames<T>( T first, T second ) { return first + second; }

In realtà non puoi scrivere questo codice perché non ci sono modi per dichiarare un'interfaccia con il file + metodo in esso.Hai fallito.

Il C++ non soffre di nessuno di questi problemi.Il compilatore non si preoccupa di passare i tipi a nessuna VM: se entrambi gli oggetti hanno una funzione .Name(), verrà compilato.Se non lo fanno, non lo farà.Semplice.

Così il gioco è fatto :-)

Altri suggerimenti

Il C++ usa raramente la terminologia “generica”.Viene invece utilizzata la parola “modelli” che è più precisa.I modelli ne descrivono uno tecnica per ottenere un disegno generico.

I modelli C++ sono molto diversi da quelli implementati sia da C# che da Java per due motivi principali.Il primo motivo è che i modelli C++ non consentono solo argomenti di tipo in fase di compilazione ma anche argomenti con valore const in fase di compilazione:i modelli possono essere forniti come numeri interi o anche come firme di funzioni.Ciò significa che puoi fare alcune cose piuttosto originali in fase di compilazione, ad es.calcoli:

template <unsigned int N>
struct product {
    static unsigned int const VALUE = N * product<N - 1>::VALUE;
};

template <>
struct product<1> {
    static unsigned int const VALUE = 1;
};

// Usage:
unsigned int const p5 = product<5>::VALUE;

Questo codice utilizza anche l'altra caratteristica distintiva dei modelli C++, ovvero la specializzazione dei modelli.Il codice definisce un modello di classe, product che ha un argomento valore.Definisce inoltre una specializzazione per quel modello che viene utilizzata ogni volta che l'argomento vale 1.Ciò mi consente di definire una ricorsione sulle definizioni dei modelli.Credo che questo sia stato scoperto per la prima volta da Andrei Alexandrescu.

La specializzazione dei modelli è importante per C++ perché consente differenze strutturali nelle strutture dei dati.I modelli nel loro insieme rappresentano un mezzo per unificare un'interfaccia tra tipi.Tuttavia, sebbene ciò sia auspicabile, non tutti i tipi possono essere trattati allo stesso modo all'interno dell'implementazione.I modelli C++ ne tengono conto.Questa è più o meno la stessa differenza che l'OOP fa tra interfaccia e implementazione con la sovrascrittura dei metodi virtuali.

I modelli C++ sono essenziali per il suo paradigma di programmazione algoritmica.Ad esempio, quasi tutti gli algoritmi per i contenitori sono definiti come funzioni che accettano il tipo di contenitore come tipo di modello e li trattano in modo uniforme.In realtà non è del tutto corretto:Il C++ non funziona sui contenitori ma piuttosto su intervalli che sono definiti da due iteratori, che puntano all'inizio e dietro la fine del contenitore.Pertanto, l'intero contenuto è circoscritto dagli iteratori:inizio <= elementi < fine.

Usare gli iteratori invece dei contenitori è utile perché permette di operare su parti di un contenitore invece che sull'insieme.

Un'altra caratteristica distintiva del C++ è la possibilità di specializzazione parziale per i modelli di classe.Ciò è in qualche modo correlato alla corrispondenza dei modelli sugli argomenti in Haskell e altri linguaggi funzionali.Ad esempio, consideriamo una classe che memorizza elementi:

template <typename T>
class Store { … }; // (1)

Funziona per qualsiasi tipo di elemento.Ma diciamo che possiamo memorizzare i puntatori in modo più efficiente rispetto ad altri tipi applicando qualche trucco speciale.Possiamo farlo entro parzialmente specializzato per tutti i tipi di puntatore:

template <typename T>
class Store<T*> { … }; // (2)

Ora, ogni volta che istanziamo un modello di contenitore per un tipo, viene utilizzata la definizione appropriata:

Store<int> x; // Uses (1)
Store<int*> y; // Uses (2)
Store<string**> z; // Uses (2), with T = string*.

Lo stesso Anders Hejlsberg ha descritto le differenze qui "Generici in C#, Java e C++".

Ci sono già molte buone risposte su Che cosa le differenze ci sono, quindi permettimi di dare una prospettiva leggermente diversa e aggiungere il Perché.

Come è già stato spiegato, la differenza principale è cancellazione del tipo, cioè.il fatto che il compilatore Java cancella i tipi generici e non finiscono nel bytecode generato.Tuttavia, la domanda è:Perché qualcuno dovrebbe farlo?Non ha senso!Oppure lo fa?

Ebbene, qual è l'alternativa?Se non implementi i generici nella lingua, dove Fare li implementi?E la risposta è:nella macchina virtuale.Il che interrompe la compatibilità con le versioni precedenti.

La cancellazione dei tipi, d'altro canto, consente di combinare client generici con librerie non generiche.In altre parole:il codice compilato su Java 5 può ancora essere distribuito su Java 1.4.

Microsoft, tuttavia, ha deciso di interrompere la compatibilità con le versioni precedenti dei farmaci generici. Quello è perché i generici .NET sono "migliori" dei generici Java.

Ovviamente i Sun non sono né idioti né codardi.Il motivo per cui si sono "irritati" è che Java era significativamente più vecchio e più diffuso di .NET quando hanno introdotto i generici.(Sono stati introdotti più o meno nello stesso periodo in entrambi i mondi.) Interrompere la compatibilità con le versioni precedenti sarebbe stato un enorme dolore.

Detto ancora in un altro modo:in Java, i generici fanno parte di Lingua (il che significa che si applicano soltanto a Java, non ad altri linguaggi), in .NET fanno parte del Macchina virtuale (il che significa che si applicano a Tutto linguaggi, non solo C# e Visual Basic.NET).

Confrontalo con funzionalità .NET come LINQ, espressioni lambda, inferenza di tipi di variabili locali, tipi anonimi e alberi delle espressioni:questi sono tutti lingua caratteristiche.Ecco perché ci sono sottili differenze tra VB.NET e C#:se quelle funzionalità facessero parte della VM, sarebbero le stesse Tutto le lingue.Ma il CLR non è cambiato:in .NET 3.5 SP1 è sempre lo stesso di .NET 2.0.È possibile compilare un programma C# che usa LINQ con il compilatore .NET 3.5 ed eseguirlo comunque in .NET 2.0, a condizione che non si utilizzino librerie .NET 3.5.Che avrebbe non funziona con generici e .NET 1.1, ma volevo funziona con Java e Java 1.4.

Seguito del mio intervento precedente.

I modelli sono uno dei motivi principali per cui il C++ fallisce in modo così abissale in termini di intellisense, indipendentemente dall'IDE utilizzato.A causa della specializzazione del modello, l'IDE non può mai essere veramente sicuro se un determinato membro esista o meno.Prendere in considerazione:

template <typename T>
struct X {
    void foo() { }
};

template <>
struct X<int> { };

typedef int my_int_type;

X<my_int_type> a;
a.|

Ora, il cursore è nella posizione indicata ed è dannatamente difficile per l'IDE dire a quel punto se e quali membri a ha.Per altri linguaggi l'analisi sarebbe semplice, ma per C++ è necessaria un po' di valutazione in anticipo.

La situazione peggiora.Cosa succede se my_int_type sono stati definiti anche all'interno di un modello di classe?Ora il suo tipo dipenderebbe da un altro argomento di tipo.E qui anche i compilatori falliscono.

template <typename T>
struct Y {
    typedef T my_type;
};

X<Y<int>::my_type> b;

Dopo averci pensato un po', un programmatore concluderebbe che questo codice è uguale a quello sopra: Y<int>::my_type risolve a int, Perciò b dovrebbe essere dello stesso tipo di a, Giusto?

Sbagliato.Nel punto in cui il compilatore tenta di risolvere questa istruzione, in realtà non lo sa Y<int>::my_type Ancora!Pertanto, non sa che questo è un tipo.Potrebbe essere qualcos'altro, ad es.una funzione membro o un campo.Ciò potrebbe dar luogo ad ambiguità (ma non nel caso presente), quindi il compilatore fallisce.Dobbiamo dirlo esplicitamente che ci riferiamo a un nome di tipo:

X<typename Y<int>::my_type> b;

Ora il codice viene compilato.Per vedere come nascono le ambiguità da questa situazione, considerare il seguente codice:

Y<int>::my_type(123);

Questa istruzione di codice è perfettamente valida e dice a C++ di eseguire la chiamata di funzione a Y<int>::my_type.Tuttavia, se my_type non è una funzione ma piuttosto un tipo, questa istruzione sarebbe comunque valida ed eseguirebbe un cast speciale (il cast in stile funzione) che spesso è un'invocazione del costruttore.Il compilatore non può dire cosa intendiamo, quindi dobbiamo chiarire le ambiguità qui.

Sia Java che C# hanno introdotto i generici dopo il rilascio del primo linguaggio.Tuttavia, esistono differenze nel modo in cui le librerie principali sono cambiate quando sono stati introdotti i farmaci generici. I generici di C# non sono solo magia del compilatore e quindi non è stato possibile generare classi di libreria esistenti senza interrompere la compatibilità con le versioni precedenti.

Ad esempio, in Java il file esistente Quadro delle collezioni era completamente genericizzato. Java non dispone di una versione generica e di una versione legacy non generica delle classi di raccolte. In un certo senso questo è molto più pulito: se hai bisogno di usare una raccolta in C# ci sono davvero pochissimi motivi per scegliere la versione non generica, ma quelle classi legacy rimangono al loro posto, ingombrando il panorama.

Un'altra differenza notevole sono le classi Enum in Java e C#. Enum di Java ha questa definizione dall'aspetto un po' tortuosa:

//  java.lang.Enum Definition in Java
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {

(vedi molto chiaro di Angelika Langer spiegazione esatta del perché questo è così.In sostanza, ciò significa che Java può fornire un accesso sicuro al tipo da una stringa al suo valore Enum:

//  Parsing String to Enum in Java
Colour colour = Colour.valueOf("RED");

Confronta questo con la versione di C#:

//  Parsing String to Enum in C#
Colour colour = (Colour)Enum.Parse(typeof(Colour), "RED");

Poiché Enum esisteva già in C# prima che i generici venissero introdotti nel linguaggio, la definizione non poteva essere modificata senza compromettere il codice esistente.Pertanto, come le raccolte, rimane nelle librerie principali in questo stato legacy.

Con 11 mesi di ritardo, ma penso che questa domanda sia pronta per alcune cose Java Wildcard.

Questa è una caratteristica sintattica di Java.Supponiamo di avere un metodo:

public <T> void Foo(Collection<T> thing)

E supponiamo che non sia necessario fare riferimento al tipo T nel corpo del metodo.Stai dichiarando un nome T e poi lo usi solo una volta, quindi perché dovresti pensare a un nome per questo?Puoi invece scrivere:

public void Foo(Collection<?> thing)

Il punto interrogativo chiede al compilatore di fingere di aver dichiarato un parametro di tipo con nome normale che deve apparire solo una volta in quel punto.

Non c'è nulla che tu possa fare con i caratteri jolly che non puoi fare anche con un parametro di tipo denominato (che è il modo in cui queste cose vengono sempre eseguite in C++ e C#).

Wikipedia ha ottimi articoli che confrontano entrambi Generici Java/C# E Generici Java/C++ modelli.IL articolo principale sui generici sembra un po' disordinato ma contiene alcune buone informazioni.

La lamentela più grande è la cancellazione dei caratteri.In questo modo, i generici non vengono applicati in fase di esecuzione. Ecco un collegamento ad alcuni documenti Sun sull'argomento.

I generici sono implementati per cancellazione del tipo:Le informazioni di tipo generico sono presenti solo al momento della compilazione, dopo di che vengono cancellate dal compilatore.

I modelli C++ sono in realtà molto più potenti delle loro controparti C# e Java poiché vengono valutati in fase di compilazione e supportano la specializzazione.Ciò consente la metaprogrammazione dei modelli e rende il compilatore C++ equivalente a una macchina di Turing (cioèdurante il processo di compilazione puoi calcolare tutto ciò che è calcolabile con una macchina di Turing).

In Java, i generici sono solo a livello di compilatore, quindi ottieni:

a = new ArrayList<String>()
a.getClass() => ArrayList

Tieni presente che il tipo di "a" è un elenco di array, non un elenco di stringhe.Quindi il tipo di una lista di banane equivarrebbe() a una lista di scimmie.

Per così dire.

Sembra che, tra le altre proposte molto interessanti, ce ne sia una sul perfezionamento dei farmaci generici e sull'interruzione della compatibilità con le versioni precedenti:

Attualmente, i generici sono implementati utilizzando ERASURE, il che significa che le informazioni di tipo generico non sono disponibili in fase di esecuzione, il che rende difficile scrivere un qualche tipo di codice.I generici sono stati implementati in questo modo per supportare la compatibilità all'indietro con il vecchio codice non generico.Reified generici renderebbe disponibili le informazioni di tipo generico in fase di esecuzione, il che romperebbe il codice non generico non genitore.Tuttavia, Neal Gafter ha proposto di rendere i tipi di reibilità solo se specificato, in modo da non rompere la compatibilità all'indietro.

A L'articolo di Alex Miller sulle proposte Java 7

NB:Non ho abbastanza punti per commentare, quindi sentiti libero di spostarlo come commento nella risposta appropriata.

Contrariamente alla credenza popolare, che non capisco mai da dove provenga, .net ha implementato veri generici senza interrompere la compatibilità con le versioni precedenti, e hanno dedicato sforzi espliciti a questo scopo.Non è necessario modificare il codice .net 1.0 non generico in codici generici solo per utilizzarlo in .net 2.0.Sia gli elenchi generici che quelli non generici sono ancora disponibili in .Net framework 2.0 anche fino alla 4.0, proprio per nient'altro che ragioni di compatibilità con le versioni precedenti.Pertanto i vecchi codici che utilizzavano ancora ArrayList non generici continueranno a funzionare e utilizzeranno la stessa classe ArrayList di prima.La compatibilità del codice con le versioni precedenti è sempre mantenuta dalla versione 1.0 fino ad ora...Quindi, anche in .net 4.0, devi comunque scegliere di utilizzare qualsiasi classe non generica da 1.0 BCL se scegli di farlo.

Quindi non penso che Java debba interrompere la compatibilità con le versioni precedenti per supportare i veri farmaci generici.

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