Quali sono le differenze tra Generics in C # e Java ... e Templates in C ++? [chiuso]


203

Uso 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. Ecc.

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

Risposte:


363

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 poi il compilatore ti impedirà di inserire cose che non sono Personnell'elenco.
Dietro le quinte il compilatore C # sta solo inserendo List<Person>il file .NET dll, ma in fase di esecuzione il compilatore JIT va e costruisce un nuovo set di codice, come se avessi scritto una classe di elenco speciale solo per contenere persone, qualcosa di simileListOfPerson .

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

Lo svantaggio di questo è 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 vecchioList 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 più o meno lo è. Il compilatore ti impedirà anche di inserire cose che non sono Personnell'elenco.

La differenza è cosa succede dietro le quinte. A differenza di C #, Java non crea uno speciale ListOfPerson- usa solo il vecchio semplice ArrayListche è sempre stato in Java. Quando togli le cose dallo schieramento, il solito Person p = (Person)foo.get(1);ballo di casting deve ancora essere fatto. Il compilatore ti sta risparmiando le pressioni dei tasti, ma la velocità di hit / casting è ancora sostenuta come sempre.
Quando le persone menzionano "Type Erasure" questo è ciò di cui stanno parlando. Il compilatore inserisce i cast per te, quindi "cancella" il fatto che deve essere un elenco Personnon solo diObject

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

Lo svantaggio è il colpo di velocità che ho citato in precedenza, e anche perché non c'è nessuna ListOfPersonpseudo-classe o qualcosa del genere nei file .class, codice che lo guarda in seguito (con riflessione o se lo estrai da un'altra raccolta dove è stato convertito Objecto così via) non può dire in alcun modo che deve essere un elenco contenente solo Persone 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>();

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

Ha più in comune con i generici di C # in quanto costruisce speciali pseudo-classespiuttosto che buttare via le informazioni sul tipo come fa java, ma è una cosa completamente diversa.

Sia C # che Java producono un output progettato per le macchine virtuali. Se scrivi del codice che contiene una Personclasse, in entrambi i casi alcune informazioni su una Personclasse andranno nel file .dll o .class, e JVM / CLR farà qualcosa con questo.

C ++ produce codice binario x86 grezzo. Tutto è non è un oggetto, e non c'è macchina virtuale sottostante che ha bisogno di sapere di una Personclasse. Non c'è boxing o unboxing e le funzioni non devono appartenere a classi, o addirittura niente.

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 generics deve sapere quali metodi sono disponibili per una classe e deve trasmetterli alla macchina virtuale. L'unico modo per dirlo è codificare la classe effettiva in 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 sa che il tipo Tfornisce effettivamente un metodo chiamato Name (). Devi dirlo - in C # come questo:

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 è cercare di scrivere una funzione che lo faccia

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

Non puoi effettivamente scrivere questo codice perché non ci sono modi per dichiarare un'interfaccia con il +metodo al suo interno. Hai fallito.

Il C ++ non soffre di nessuno di questi problemi. Al compilatore non interessa passare i tipi a nessuna macchina virtuale: se entrambi gli oggetti hanno una funzione .Name (), verrà compilato. Se non lo fanno, non lo faranno. Semplice.

Così il gioco è fatto :-)


8
Lo pseudoclasess generato per i tipi di riferimento in C # condivide la stessa implementazione in modo da non ottenere esattamente ListOfPeople. Dai
Piotr Czapla

4
No, è possibile non compilare il codice Java 5 utilizzando farmaci generici, e lo hanno eseguito su vecchio 1.4 VM (almeno il Sun JDK non implementa questa. Alcuni strumenti 3rd party fanno.) Che cosa si può fare è l'uso precedentemente compilato 1.4 JAR da Codice 1.5 / 1.6.
Finlandia

4
Mi oppongo alla dichiarazione che non puoi scrivere int addNames<T>( T first, T second ) { return first + second; }in C #. Il tipo generico può essere limitato a una classe anziché a un'interfaccia e c'è un modo per dichiarare una classe con l' +operatore al suo interno.
Mashmagar

4
@AlexanderMalakhov non è idiomatico apposta. Il punto non era quello di istruire sugli idiomi del C ++, ma di illustrare come una parte di codice con lo stesso aspetto viene gestita in modo diverso da ogni lingua. Questo obiettivo sarebbe stato più difficile da raggiungere quanto più diverso è l'aspetto del codice
Orion Edwards il

3
@phresnel Sono d'accordo in linea di principio, ma se avessi scritto quel frammento in C ++ idiomatico, sarebbe stato molto meno comprensibile per gli sviluppatori C # / Java, e quindi (credo) avrei fatto un lavoro peggiore nello spiegare la differenza. Accettiamo di non essere d'accordo su questo :-)
Orion Edwards

61

Il C ++ utilizza raramente la terminologia "generica". Viene invece utilizzata la parola "modelli", che è più accurata. I modelli descrivono una tecnica per ottenere un design generico.

I modelli C ++ sono molto diversi da ciò che implementano sia C # che Java per due motivi principali. La prima ragione è che i modelli C ++ non consentono solo argomenti di tipo in fase di compilazione, ma anche argomenti valore const in fase di compilazione: i modelli possono essere forniti come numeri interi o anche come firme di funzione. Ciò significa che puoi fare alcune cose piuttosto strane in fase di compilazione, ad esempio 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, productche ha un argomento valore. Definisce anche una specializzazione per quel modello che viene utilizzata ogni volta che l'argomento restituisce 1. Ciò mi consente di definire una ricorsione sulle definizioni del modello. 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 sono un mezzo per unificare un'interfaccia tra i tipi. Tuttavia, sebbene ciò sia desiderabile, 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 l'interfaccia e l'implementazione con l'override 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 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 al posto dei contenitori è utile perché permette di operare su parti di un contenitore invece che sul tutto.

Un'altra caratteristica distintiva del C ++ è la possibilità di una specializzazione parziale per i modelli di classe. Questo è in qualche modo correlato alla corrispondenza dei modelli sugli argomenti in Haskell e in 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 di altri tipi applicando qualche trucco speciale. Possiamo farlo specializzandoci parzialmente 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*.

A volte ho desiderato che la funzionalità generics in .net potesse consentire a cose oltre ai tipi di essere utilizzate come chiavi. Se gli array di tipo valore facessero parte del Framework (sono sorpreso che non lo siano, in un certo senso, data la necessità di interagire con API precedenti che incorporano array di dimensioni fisse all'interno di strutture), sarebbe utile dichiarare un classe che conteneva alcuni singoli elementi e quindi un array di tipo valore la cui dimensione era un parametro generico. Così com'è, il più vicino possibile è avere un oggetto di classe che contiene i singoli elementi e quindi contiene anche un riferimento a un oggetto separato che contiene l'array.
supercat

@supercat Se interagisci con l'API legacy l'idea è di utilizzare il marshalling (che può essere annotato tramite attributi). Il CLR non ha comunque array di dimensioni fisse, quindi avere argomenti di modello non di tipo non sarebbe di aiuto qui.
Konrad Rudolph

Immagino che quello che trovo sconcertante è che sembrerebbe che avere array di tipi di valore di dimensioni fisse non avrebbe dovuto essere difficile e avrebbe consentito a molti tipi di dati di essere marshalling per riferimento piuttosto che per valore. Mentre marshal-by-value può essere utile nei casi che non possono essere gestiti in nessun altro modo, considererei marshal-by-ref superiore in quasi tutti i casi in cui è utilizzabile, consentendo così a tali casi di includere struct con fixed -array di dimensioni sarebbero sembrate una caratteristica utile.
supercat

A proposito, un'altra situazione in cui sarebbero utili parametri generici non di tipo sarebbe con tipi di dati che rappresentano quantità dimensionate. Si potrebbero includere informazioni dimensionali all'interno di istanze che rappresentano quantità, ma avere tali informazioni all'interno di un tipo consentirebbe di specificare che una raccolta dovrebbe contenere oggetti che rappresentano una particolare unità dimensionata.
supercat


18

Ci sono già molte buone risposte su quali sono le differenze, quindi lasciatemi dare una prospettiva leggermente diversa e aggiungere il perché .

Come già spiegato, la differenza principale è la cancellazione del tipo , ovvero 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 sì?

Bene, qual è l'alternativa? Se non implementare generici nella lingua, in cui si fa di implementare loro? E la risposta è: nella macchina virtuale. Che rompe la compatibilità con le versioni precedenti.

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

Microsoft, tuttavia, ha deciso di interrompere la compatibilità con le versioni precedenti per i generici. Ecco perché .NET Generics è "migliore" di Java Generics.

Naturalmente, Sun non sono idioti o codardi. Il motivo per cui si sono "tirati indietro" è che Java era significativamente più vecchio e più diffuso di .NET quando hanno introdotto i generici. (Sono stati introdotti all'incirca contemporaneamente in entrambi i mondi.) Rompere la compatibilità con le versioni precedenti sarebbe stato un enorme dolore.

In altre parole: in Java, i generici fanno parte del linguaggio (il che significa che si applicano solo a Java, non ad altri linguaggi), in .NET fanno parte della macchina virtuale (il che significa che si applicano a tutti i linguaggi, non solo C # e Visual Basic.NET).

Confronta questo con le funzionalità .NET come LINQ, espressioni lambda, inferenza di tipi di variabili locali, tipi anonimi e alberi di espressioni: queste sono tutte funzionalità del linguaggio . Ecco perché ci sono sottili differenze tra VB.NET e C #: se queste funzionalità facessero parte della VM, sarebbero le stesse in tutte le lingue. Ma il CLR non è cambiato: è sempre lo stesso in .NET 3.5 SP1 come in .NET 2.0. È possibile compilare un programma C # che utilizza LINQ con il compilatore .NET 3.5 ed eseguirlo comunque su .NET 2.0, a condizione che non si utilizzi alcuna libreria .NET 3.5. Che sarebbe non lavoro con i generici e .NET 1.1, ma dovrebbe funzionare con Java e Java 1.4.


3
LINQ è principalmente una funzionalità di libreria (sebbene C # e VB abbiano anche aggiunto lo zucchero di sintassi insieme ad esso). Qualsiasi linguaggio destinato a 2.0 CLR può ottenere il pieno utilizzo di LINQ semplicemente caricando l'assembly System.Core.
Richard Berg

Sì, scusa, avrei dovuto essere più chiaro rispetto. LINQ. Mi riferivo alla sintassi della query, non agli operatori di query standard monadici, ai metodi di estensione LINQ o all'interfaccia IQueryable. Ovviamente, puoi usare quelli da qualsiasi linguaggio .NET.
Jörg W Mittag

1
Sto pensando a un'altra opzione per Java. Anche Oracle non vuole rompere la compatibilità con le versioni precedenti, può comunque fare qualche trucco del compilatore per evitare che le informazioni sul tipo vengano cancellate. Ad esempio, ArrayList<T>può essere emesso come un nuovo tipo denominato internamente con un Class<T>campo statico (nascosto) . Finché la nuova versione di lib generica è stata distribuita con il codice da 1.5+ byte, sarà in grado di funzionare su 1.4-JVM.
Earth Engine

14

Follow-up del mio precedente intervento.

I modelli sono uno dei motivi principali per cui il C ++ fallisce in modo così abissale all'intellisense, indipendentemente dall'IDE utilizzato. A causa della specializzazione del modello, l'IDE non può mai essere veramente sicuro se un dato membro esiste 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 si trova nella posizione indicata ed è dannatamente difficile per l'IDE dire a quel punto se e cosa hanno i membri a. Per altri linguaggi l'analisi sarebbe semplice, ma per C ++, è necessaria in anticipo un bel po 'di valutazione.

La situazione peggiora. E se my_int_typefossero stati definiti anche all'interno di un modello di classe? Ora il suo tipo dipenderà 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 un po 'di riflessione, un programmatore concluderebbe che questo codice è lo stesso del precedente: si Y<int>::my_typerisolve in int, quindi bdovrebbe essere dello stesso tipo di a, giusto?

Sbagliato. Nel punto in cui il compilatore cerca di risolvere questa istruzione, in realtà non lo sa Y<int>::my_typeancora! Pertanto, non sa che questo è un tipo. Potrebbe essere qualcos'altro, ad esempio una funzione membro o un campo. Ciò potrebbe dare adito ad ambiguità (sebbene non nel caso presente), quindi il compilatore fallisce. Dobbiamo dirgli esplicitamente che ci riferiamo a un nome di tipo:

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

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

Y<int>::my_type(123);

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


2
Sono abbastanza d'accordo. C'è qualche speranza, però. Il sistema di completamento automatico e il compilatore C ++ devono interagire molto strettamente. Sono abbastanza sicuro che Visual Studio non avrà mai una tale funzionalità, ma le cose potrebbero accadere in Eclipse / CDT o in qualche altro IDE basato su GCC. SPERANZA ! :)
Benoît

6

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

Ad esempio, in Java l'esistente Collections Framework stato completamente generico . Java non dispone sia di una versione generica che di una precedente non generica delle classi di raccolte. In un certo senso questo è molto più pulito: se è necessario utilizzare una raccolta in C # ci sono davvero pochi motivi per utilizzare la versione non generica, ma quelle classi legacy rimangono al loro posto, ingombrando il paesaggio.

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

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

(vedi Angelika Langer molto chiaro spiegazione motivo esatto in cui è così. Essenzialmente, questo significa che Java può dare un accesso sicuro ai tipi 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 dell'introduzione dei generics nel linguaggio, la definizione non poteva cambiare senza interrompere il codice esistente. Quindi, come le raccolte, rimane nelle librerie principali in questo stato legacy.


Anche i generici di C # non sono solo la magia del compilatore, il compilatore può fare ulteriore magia per generare la libreria esistente. Non v'è alcun motivo per cui hanno bisogno di rinominare ArrayListper List<T>e metterlo in un nuovo spazio dei nomi. Il fatto è che, se una classe fosse visualizzata nel codice sorgente in ArrayList<T>quanto diventerà un nome di classe generato dal compilatore diverso nel codice IL, quindi non possono verificarsi conflitti di nome.
Earth Engine

4

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

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

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

E supponiamo di non aver bisogno di 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 esso? Puoi invece scrivere:

public void Foo(Collection<?> thing)

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

Non c'è niente che puoi 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 #).


2
Altri 11 mesi di ritardo ... Ci sono cose che puoi fare con i caratteri jolly Java che non puoi fare con i parametri di tipo denominato. Puoi farlo in Java: class Foo<T extends List<?>>e usare, Foo<StringList>ma in C # devi aggiungere quel parametro di tipo extra: class Foo<T, T2> where T : IList<T2>e usare il goffo Foo<StringList, String>.
R. Martinho Fernandes


1

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

I generici sono implementati dalla cancellazione del tipo: le informazioni sul tipo generico sono presenti solo in fase di compilazione, dopodiché vengono cancellate dal compilatore.


1

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 Meta-Programmazione dei Modelli e rende il compilatore C ++ equivalente a una macchina di Turing (cioè durante il processo di compilazione è possibile calcolare tutto ciò che è calcolabile con una macchina di Turing).


1

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

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

Si noti che il tipo di "a" è un elenco di array, non un elenco di stringhe. Quindi il tipo di un elenco di banane sarebbe uguale () a un elenco di scimmie.

Per così dire.


1

Sembra che, tra le altre proposte molto interessanti, ce ne sia una sul perfezionamento dei generici e sulla rottura della retrocompatibilità:

Attualmente, i generici vengono implementati utilizzando la cancellazione, il che significa che le informazioni sul tipo generico non sono disponibili in fase di esecuzione, il che rende difficile scrivere qualche tipo di codice. I generici sono stati implementati in questo modo per supportare la compatibilità con le versioni precedenti del codice non generico precedente. I generici reificati renderebbero disponibili le informazioni sul tipo generico in fase di esecuzione, il che romperebbe il codice non generico precedente. Tuttavia, Neal Gafter ha proposto di rendere i tipi reificabili solo se specificato, in modo da non rompere la retrocompatibilità.

in un articolo di Alex Miller su Java 7 proposte


0

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

Contrariamente alla credenza popolare, che non capisco mai da dove provenga, .net ha implementato i veri generici senza rompere la compatibilità con le versioni precedenti, e hanno speso sforzi espliciti per questo. Non è necessario modificare il codice .net 1.0 non generico in generics solo per essere utilizzato 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, esattamente per nient'altro che ragioni di compatibilità con le versioni precedenti. Pertanto i vecchi codici che ancora utilizzavano ArrayList non generici continueranno a funzionare e utilizzeranno la stessa classe ArrayList di prima. La compatibilità con il codice all'indietro è sempre mantenuta dalla 1.0 fino ad ora ... Quindi, anche in .net 4.0, devi ancora scegliere di utilizzare qualsiasi classe non generica da 1.0 BCL se scegli di farlo.

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


Non è il tipo di compatibilità con le versioni precedenti di cui si parla. L'idea è la compatibilità all'indietro per il runtime : il codice scritto utilizzando generics in .NET 2.0 non può essere eseguito su versioni precedenti di .NET framework / CLR. Allo stesso modo, se Java introducesse generici "veri", il codice Java più recente non sarebbe in grado di funzionare su JVM meno recenti (perché richiede modifiche interrotte al bytecode).
tzaman

Questo è .net, non generici. Richiede sempre la ricompilazione per targetizzare una versione CLR specifica. C'è la compatibilità del bytecode, c'è la compatibilità del codice. Inoltre, stavo rispondendo specificamente per quanto riguarda la necessità di convertire il vecchio codice che utilizzava il vecchio List per utilizzare il nuovo Elenco dei generici, il che non è affatto vero.
Sheepy

1
Penso che la gente parli di compatibilità in avanti . Cioè .net 2.0 codice da eseguire su .net 1.1, che si interromperà perché il runtime 1.1 non sa nulla della "pseudo-classe" 2.0. Non dovrebbe quindi essere che "java non implementa il vero generico perché vuole mantenere la compatibilità con le versioni successive"? (piuttosto che all'indietro)
Sheepy

I problemi di compatibilità sono impercettibili. Non credo che il problema fosse che l'aggiunta di generici "reali" a Java avrebbe influenzato tutti i programmi che utilizzano versioni precedenti di Java, ma piuttosto quel codice che utilizzava generici "nuovi migliorati" avrebbe avuto difficoltà a scambiare tali oggetti con codice più vecchio che non sapeva nulla dei nuovi tipi. Supponiamo, ad esempio, che un programma abbia un ArrayList<Foo>che vuole passare a un metodo più vecchio che dovrebbe popolare un ArrayListcon istanze di Foo. Se un ArrayList<foo>non è un ArrayList, come si fa a farlo funzionare?
supercat
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.